Reputation:
I will try to explain my problem on cars. I have AbstractCar and the users (developers) of my library will create many their ConcreteCars. This AbstractCar has state and this state is very important for right working of the library! Only the car can control its state (no any Drivers etc). The state changes in methods start/stop at the beginning and at the end of the methods. Besides all cars must implement interface Car.
public enum State{
STARTING, STARTED, STOPPING, STOPPED
}
public interface Car{
public void start();
public void stop();
public State getState();
}
I tried two variants.
Variant 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);
}
}
At variant 1 the user of the library will have to remember to change the state. If he forgets to do it, then there will be a bug in the code.
Variant 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 variant 2 the user can't forget about state because it is already outside his control, but if I have many states and many methods in which they can be changed this is not a very good way.
Could anyone advise any pattern or technologies how to solve such problem?
Upvotes: 5
Views: 829
Reputation: 12450
If you want to retain full control over which state the car will be at a given moment and which transitions are allowed, the second approach is the basic pattern to use.
You may modify the way you call the subclass' code (be it by calling an abstract method, or some other kind of callback), but the basic pattern will be the same - your AbstractCar
's code will contain the logic of states and transitions, with defined points where "external" code may be called. These are also sometimes referred to as "hooks".
A (perhaps a bit far-fetched) example of such approach is JSF life-cycle where the request goes through a complex workflow and in some given phases (e.g. validation) user-supplied code may be executed - but it has no way to directly set the state of the request.
If you want to allow your users (i.e. subclass authors) to be able to affect the car's state, you can do so in a controlled way by accepting a return value from the callback that affects the following state transition, or in some cases simply by doing proper error-handling:
public final void start(){
state=STARTING;
try {
doOnStart();
state=STARTED;
} catch (RuntimeException e) {
// handle error
state=STOPPED;
}
}
Upvotes: 0
Reputation: 131496
In variant 2 the user can't forget about state because it is already outside his control
And is it desirable ?
public abstract class AbstractCar implements Car{
...
public final void start(){
state=...;
doOnStart();
state=...;
}
...
}
In the abstract class you determine for the concrete classes the state that they should be used.
It doesn't seem a very flexible solution for concrete classes since a state change should depend on the context that may change in the start() method.
You should use abstract method in AbstractCar to both allow and force concrete classes to choose how define their state such as :
Suppose this concrete 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 could also use Javadoc to document correctly the API of your classes and the responsibilities of concrete classes in order to favor a good use of it.
Upvotes: 0
Reputation: 76
Use a state design pattern. https://sourcemaking.com/design_patterns/state
Keep your AbstractCar as a Context and use it to manage the state of the car.
I do an example as following. Hopefully I understand it 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
}
}
Upvotes: 0