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 State
s.
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
OffLeachState
with a differentOffLeashState
that 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
World
class 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 theDog
class resistant to change. Ie, every time I add a newDogState
class, I also have to make sure thatDog
has a getter for it. Also, theWorld
doesn't know about these changes, so it doesn't log them nor notify observers.Should there be a class called
DogStates
and I can runDogStates.getBarkingState()
? Again, similar problems to the one above.Should they be a part of the
World
class? For example,world.setDogState(dog, world.getDogBarkingState()
? This would solve the logging/updating problem, but puts too much responsibility on theWorld
class.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 aDog
object 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 Request
s 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.