Java: state and inheritance design pattern

I will try to explain my problem on cars. I have an AbstractCar, and the users (developers) of my library will create a lot of their ConcreteCars. This AbstractCar has a state, and this state is very important for the library to work properly! Only a car can manage its state (no drivers, etc.). The state changes in the start / stop methods at the beginning and at the end of methods. In addition, all vehicles must implement the Car interface.

public enum State{ STARTING, STARTED, STOPPING, STOPPED } public interface Car{ public void start(); public void stop(); public State getState(); } 

I tried two options.

Option 1

 public abstract class AbstractCar implements Car{ private State state; public void setState(State state){...} public State getState(){...} } public class ConcreteCar extends AbstractCar{ @Override public void start(){ setState(stateK); ... setState(stateN); } @Override public void stop(){ setState(stateR); ... setState(stateO); } } 

In option 1, the library user will have to remember the state. If he does not, then there will be an error in the code.

Option 2

 public abstract class AbstractCar implements Car{ private State state; protected void doOnStart(){ } protected void doOnStop(){ } public final void start(){ state=...; doOnStart(); state=...; } public final void stop(){ state=...; doOnStop(); state=...; } } public class ConcreteCar extends AbstractCar{ @Override protected void doOnStart(){ .... } @Override protected void doOnStop(){ ... } } 

In option 2, the user cannot forget about the state, because he is already out of his control, but if I have many states and many methods in which they can be changed, this is not a good way.

Can anyone advise any template or technology how to solve such a problem?

+5
source share
3 answers

If you want to maintain full control over what state the car will be in at the moment and what transitions are allowed, the second approach is the main template for use.

You can change the way you call the subclass code (whether by calling an abstract method or some other callback), but the basic template will be the same - your AbstractCar code will contain logic of states and transitions, with certain points where there may be The "external" code is called. They are also sometimes called “hooks”.

A (perhaps a little far-fetched) example of this approach is the JSF life cycle , where the request goes through a complex workflow and in some given phases (e.g. validation) the user code can be executed - but it does not have the ability to directly establish the status of the request.

If you want your users (for example, subclass authors) to be able to influence the state of the car, you can do it in a controlled way by accepting a return value from the callback that affects the next state transition, or in some cases, just by doing the right error handling :

 public final void start(){ state=STARTING; try { doOnStart(); state=STARTED; } catch (RuntimeException e) { // handle error state=STOPPED; } } 
+1
source

Use a status template. https://sourcemaking.com/design_patterns/state

Store your AbstractCar as a context and use it to control the state of the car.

I am doing an example as follows. I hope I understand that correctly.

 public interface IState { public void changeState(Car inContext); public void doSomething(); } public interface Car { enum CarState{ start, stop, auto } public void setState(CarState state); } public class AbstractCar implements Car { IState m_currentState; IState startState = new StartState(); IState stopState = new StopState(); IState autoState = new AutoNavigateState(); public AbstractCar() { m_currentState = stopState; } public void start() { setState(CarState.start); m_currentState.doSomething(); m_currentState.changeState(this); } public void stop() { setState(CarState.stop); m_currentState.doSomething(); m_currentState.changeState(this); } public void autoNavigate() { setState(CarState.auto); m_currentState.doSomething(); m_currentState.changeState(this); } public void setState(CarState state) { if (state == CarState.start) { m_currentState = startState; } else if (state == CarState.stop) { m_currentState = stopState; } else { m_currentState = autoState; } } } public class StartState implements IState { @Override public void changeState(Car car) { car.setState(CarState.stop); } @Override public void doSomething() { // TODO Auto-generated method stub } } public class StopState implements IState{ @Override public void changeState(Car car) { car.setState(CarState.start); } @Override public void doSomething() { // TODO Auto-generated method stub } } public class AutoNavigateState implements IState{ @Override public void changeState(Car inContext) { // TODO Auto-generated method stub } @Override public void doSomething() { // TODO Auto-generated method stub } } 
0
source

In option 2, the user cannot forget about the state, because he is already out of his control

And is it desirable?

 public abstract class AbstractCar implements Car{ ... public final void start(){ state=...; doOnStart(); state=...; } ... } 

In an abstract class, you define for specific classes the state in which they should be used.
This does not seem to be a very flexible solution for specific classes, since the state change should depend on the context that can change in the start () method.

You must use the abstract method in AbstractCar to enable and force specific classes to choose how to determine their state, for example:

Assume this particular class:

 public abstract class AbstractCar implements Car{ ... public abstract State getStateBeforeStart(); public abstract State getStateAfterStart(); ... public final void start(){ state = getStateBeforeStart(); doOnStart(); state = getStateAfterStart(); } ... } 

You can also use Javadoc to properly document the API of your classes and the responsibilities of specific classes to make good use of it.

0
source

Source: https://habr.com/ru/post/1263101/


All Articles