I am unsure as to what the best OO design approach should be regarding a particular State pattern I am implementing. Please consider the following:
public class World {
private Animal dog_;
private Animals cats_;
…..
public void sendDogRequest(DogRequest request) {
dog_.sendRequest(request);
}
…
public Cat getCat(String catName) {
…
return cat;
}
...
}
public class Animal<RequestType extends Request, StateType extends State> {
private State<StateType> currentState_;
….
public void sendRequest(RequestType request) {
request.sendToState(currentState_);
}
public void setState(StateType state) {
currentState_ = state;
}
}
public class Dog extends Animal<DogState> {
…
}
public class DogState extends State {
public DogState(Dog dog) {
…
}
public void seeCat(Cat cat) { }
}
public class OnLeashState extends DogState {
public void seeCat(Cat cat) {
dog.setState(new BarkingState());
}
}
public class OffLeashState extends DogState {
public void seeCat(Cat cat) {
dog.setState(new ChasingAfterAnimalState(cat));
cat.sendRequest(new RunAwayRequest(cat));
}
}
public interface Request<StateType extends State> {
public void sendToState(StateType state);
}
public class DogRequest extends Request<DogState> { }
public class SeeCatRequest extends DogRequest {
private Cat cat_;
public SeeCatRequest(Cat cat) {
cat_ = cat;
}
public void sendToState(DogState state) {
state.seeCat(state);
}
}
public class Controller() {
public Controller(World model, View view) {
…
}
...
public void catSelected(String catName) {
Cat cat = world.getCat(catName);
Dog dog = world.getDog();
world.sendDogRequest(new SeeCatRequest(cat));
}
…
}
My area of hesitation is with the usages of the word new here, ie. instantiating a new SomeState() with another State, or new SomeRequest() within the Controller or another State. It seems to me that this would produce high coupling between the States and their siblings, as well as the Controller and States.
The requirements are as follows:
- It MUST be possible to add new States, for example adding a
SniffingState. It also MUST be possible to replace existing States with new ones. For example, I should be able to replace
OffLeachStatewith a differentOffLeashStatethat performs a different action. For example (for some reason the code won't format):public class OffLeachState2 extends DogState {
public void seeCat(Cat cat) {
if (dog.knows(cat)) {
// dog changes to "PlayWithCatState"
// cat gets a "PlayWithDog" request
} else {
// dog changes to "ChaseAnimalState"
}
}
}Finally, all changes within the
Worldclass MUST be logged. That means that the World class has a logger which is keeping track of everything that is going on. This is also because the World class is a model, and has to fire off anotifyObservers()so that the view knows to do something.
My question is, where should the states, requests etc be stored? For example:
Should there be state "getters" in
Dog? for example,dog.getBarkingState(),dog.getOnLeashState(), etc? This seems to make sense, but it doesn't make theDogclass resistant to change. Ie, every time I add a newDogStateclass, I also have to make sure thatDoghas a getter for it. Also, theWorlddoesn't know about these changes, so it doesn't log them nor notify observers.Should there be a class called
DogStatesand I can runDogStates.getBarkingState()? Again, similar problems to the one above.Should they be a part of the
Worldclass? For example,world.setDogState(dog, world.getDogBarkingState()? This would solve the logging/updating problem, but puts too much responsibility on theWorldclass.Should it be some combination thereof, for example
world.setState(dog, dog.getBarkingState()? This COULD be good, but doesn't assure type safety. For example, I could pass in aDogobject with aCatState, and it wouldn't know the difference.
Solution #4 seems the best to me, but I would like some other opinions about this issue.
The same question applies to the Request object. I originally wanted to send Requests by Strings which were associated with an object, for example world.sendRequest(dog, DogRequests.SEE_CAT), but then I couldn't pass the cat object as an argument.
Thank you very much for your time!
1.) This looks like a programming exam question. In such scenarios, if unsure what to do, use a Pattern! So every State should be generated by a StateFactory and give the Factory instance some information about the World so it can decide which specific State instance to create.
Here's the logging stuff:
And that Factory stuff (probably a bit scatterbrained, Generics might not work correctly ;o).
2.) If you want the World to know everything happening in it, you could substitute the state setters whith messages and let the World instance listen to everybody's state changes.