Разделение состояний с помощью шаблона состояний

Я не уверен, каким должен быть наилучший подход к объектно-ориентированному проектированию в отношении конкретного шаблона состояния, который я реализую. Пожалуйста, обратите внимание на следующее:

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));
    }
    …
}

Моя область колебаний связана с использованием слова new здесь, т.е. создание экземпляра new SomeState() с другим состоянием или new SomeRequest() в пределах Controller или другого State. Мне кажется, что это приведет к сильной связи между Штатами и их братьями и сестрами, а также Controller и State.

Требования следующие:

  1. ДОЛЖНА быть возможность добавлять новые состояния, например, добавление SniffingState.
  2. Также ДОЛЖНА быть возможна замена существующих состояний новыми. Например, я должен иметь возможность заменить OffLeachState другим OffLeashState, выполняющим другое действие. Например (по какой-то причине код не форматируется):

    public class OffLeachState2 extends DogState {
    public void seeCat(Cat cat) {
    if (dog.knows(cat)) {
    // собака меняется на "PlayWithCatState"
    // кошка получает " PlayWithDog" request
    } else {
    // собака меняется на "ChaseAnimalState"
    }
    }
    }

  3. Наконец, все изменения в классе World ДОЛЖНЫ быть зарегистрированы. Это означает, что класс World имеет регистратор, который отслеживает все, что происходит. Это также связано с тем, что класс World является моделью и должен запускать notifyObservers(), чтобы представление знало, что нужно делать.

Мой вопрос: где должны храниться состояния, запросы и т.д.? Например:

  1. Должны ли быть "геттеры" состояния в Dog? например, dog.getBarkingState(), dog.getOnLeashState() и т.д.? Это кажется логичным, но это не делает класс Dog устойчивым к изменениям. Т.е. каждый раз, когда я добавляю новый класс DogState, я также должен убедиться, что Dog имеет для него геттер. Кроме того, World не знает об этих изменениях, поэтому не регистрирует их и не уведомляет наблюдателей.

  2. Должен ли быть класс под названием DogStates, и я могу запустить DogStates.getBarkingState()? Опять же, проблемы, аналогичные описанным выше.

  3. Должны ли они быть частью класса World? Например, world.setDogState(dog, world.getDogBarkingState()? Это решит проблему регистрации/обновления, но возлагает слишком большую ответственность на класс World.

  4. Должна ли это быть какая-то их комбинация, например world.setState(dog, dog.getBarkingState()? Это МОЖЕТ быть хорошо, но не гарантирует безопасность типов. Например, я могу передать объект Dog с CatState, и он не заметит разницы.

Решение №4 кажется мне лучшим, но хотелось бы и других мнений по этому вопросу.

Тот же вопрос относится и к объекту Request. Первоначально я хотел отправить Requests строками, которые были связаны с объектом, например world.sendRequest(dog, DogRequests.SEE_CAT), но тогда я не мог передать объект cat в качестве аргумента.

Большое спасибо за уделенное время!


person Jean of mArc    schedule 28.10.2011    source источник


Ответы (1)


1.) Это похоже на экзаменационный вопрос по программированию. В таких случаях, если вы не знаете, что делать, используйте шаблон! Таким образом, каждое состояние должно генерироваться StateFactory и предоставлять экземпляру Factory некоторую информацию о мире, чтобы он мог решить, какой конкретный экземпляр состояния создать.

Вот материалы для ведения журнала:

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 ...
  }
...

И этот Фабричный материал (вероятно, немного легкомысленный, дженерики могут работать неправильно ;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.) Если вы хотите, чтобы мир знал обо всем, что в нем происходит, вы можете заменить установщики состояния сообщениями и позволить экземпляру мира прослушивать все изменения состояния.

person chabicht    schedule 28.10.2011
comment
Большое спасибо за ваш быстрый ответ! Не могли бы вы включить код в качестве примера? Для 1 я понимаю, что это будет включать что-то вроде «dogStateFactory.createBarkingState ()»? Для 2 вы бы поставили «notifyWorld()» после установки нового состояния? - person Jean of mArc; 28.10.2011
comment
Ok. Посмотрим, что выплюнет мой обедневший мозг... Я отредактирую свой ответ выше. - person chabicht; 28.10.2011
comment
Спасибо! О, и, возможно, мне стоит начать писать экзамены по программированию. :) Я придумал весь сценарий, чтобы выделить проблему, с которой я столкнулся, в более широком контексте. - person Jean of mArc; 28.10.2011
comment
Вау, большое спасибо за всю ту работу, которую вы вложили в свои примеры! Ты должен написать учебник или что-то в этом роде, ха-ха. Это определенно должно дать мне некоторые идеи относительно того, что я должен делать. Спасибо еще раз! - person Jean of mArc; 29.10.2011
comment
Я обязательно повыслю ваш ответ, как только у меня будет достаточно очков репутации. :) - person Jean of mArc; 29.10.2011
comment
Я не уверен, уместна ли такая похвала. :о) - person chabicht; 29.10.2011
comment
Если вы не знаете, что делать, используйте шаблон. -- Это все равно, что сказать, что если вы не знаете, что делать с проблемой в вашем доме, используйте клейкую ленту. - person Fuhrmanator; 08.01.2015
comment
Шаблон состояния имеет большую сложность для инкапсуляции логики состояния для всех переходов (операций). В книге GoF говорится, что его следует применять, когда операции имеют большие составные условные операторы, зависящие от состояния объекта. См. angry-architect.blogspot.ca/2006/08. / для более простых альтернатив, если ваши операции просты. - person Fuhrmanator; 08.01.2015
comment
@Fuhrmanator если вы не знаете, что делать с проблемой в вашем доме, используйте клейкую ленту — Что ж, это хороший совет, не так ли? - person MC Emperor; 05.04.2017