Reputation: 23
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:
SniffingState
.It also MUST be possible to replace existing States with new ones. For example, I should be able to replace OffLeachState
with a different OffLeashState
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 a notifyObservers()
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 the Dog
class resistant to change. Ie, every time I add a new DogState
class, I also have to make sure that Dog
has a getter for it. Also, the World
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 run DogStates.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 the World
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 a Dog
object with a CatState
, 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!
Upvotes: 2
Views: 913
Reputation: 107
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:
public class World implements StateChangeListener {
private Animal dog_;
private Animals cats_;
private final List<StateChangeListener> listeners = new ArrayList<StateChangeListener>();
public World() {
listeners.add(this);
}
// Instead of sending DogRequests to Dogs via the sendDogRequest method:
public <RequestType extends Request> void sendRequest(
Animal<RequestType, ?> animal, Request<RequestType> request) {
animal.sendRequest(request);
for(StateChangeListener listener : listeners) {
listener.stateChanged(animal, request);
}
}
public void stateChanged(Animal<?, ?> animal, State<?> state) {
// ... log here ...
}
...
And that Factory stuff (probably a bit scatterbrained, Generics might not work correctly ;o).
public enum LocationEnum {
HOME, PARK, POND, FOREST
}
public interface StateFactory<StateType extends State> {
State<StateType> create(Animal<StateType, ?> animal, Context context);
}
// Do stuff Dogs do.
public class DogStateFactory<DogState> {
public State<DogState> create(Animal<DogState, ?>, Context context) {
if(context.currentAnimalLocation==LocationEnum.POND) {
return new IgnoreEverythingState();
}else if(context.currentAnimalLocation==LocationEnum.HOME){
return new PerpetualBarkState();
}else {
return new FollowEveryCatState();
}
}
}
public class Animal<RequestType extends Request, StateType extends State> {
private StateFactory<StateType> stateFactory;
private State<StateType> currentState_;
public void sendRequest(Request<RequestType> request) {
request.sendToState(currentState_);
}
// A specific animal knows what it wants to do, depending on it's current
// state and it's situational context. We don't want other animals
// to set the state for us.
public void determineState() {
currentState_ = stateFactory.create(this, new Context(...));
// One might want to extend the messaging stuff in a way that
// the World instance can log this state change.
}
}
public class Dog extends Animal<DogRequest, DogState> {
public Dog() {
this.stateFactory = new DogStateFactory<DogState>();
}
}
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.
Upvotes: 0