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

#java #design-patterns #structure #state-pattern

#java #шаблоны проектирования #структура #шаблон состояния

Вопрос:

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

 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 здесь, т. Е.. создание экземпляра a new SomeState() с другим состоянием или new SomeRequest() внутри Controller или другого State . Мне кажется, что это приведет к высокой связи между состояниями и их братьями и сестрами, а также Controller и State s .

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

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

    открытый класс OffLeachState2 расширяет DogState {
    public void seeCat(Cat cat) {
    if (dog.knows(cat)) {
    // dog изменяется на «PlayWithCatState»
    // cat получает запрос «PlayWithDog»
    } else {
    // dog изменяется на «ChaseAnimalState»
    }
    }
    }

  3. Наконец, все изменения внутри World класса ДОЛЖНЫ быть зарегистрированы. Это означает, что у мирового класса есть регистратор, который отслеживает все, что происходит. Это также связано с тем, что World class является моделью и должен запускать a 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 объект с помощью a CatState , и он не почувствовал бы разницы.

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

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

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

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

Комментарии:

1. Большое вам спасибо за ваш быстрый ответ! Не могли бы вы включить некоторый код в качестве примера? Для 1, насколько я понимаю, это будет включать что-то вроде ‘dogStateFactory.createBarkingState ()’? Для 2 вы бы поставили ‘notifyWorld ()’ после установки нового состояния?

2. ОК. Давайте посмотрим, что выплевывает мой обеденный мозг… Собираюсь отредактировать свой ответ выше.

3. Спасибо! О, и, возможно, мне следует начать писать экзамены по программированию. 🙂 Я придумал весь сценарий, чтобы изолировать проблему, с которой я сталкиваюсь, в более широком контексте.

4. Вау, большое вам спасибо за всю ту работу, которую вы вложили в свои примеры! Вы должны написать учебник или что-то в этом роде, ха-ха. Это определенно должно дать мне некоторые идеи относительно того, что я должен делать. Еще раз спасибо!

5. Я обязательно увеличу ваш ответ на 1, как только у меня будет достаточно очков репутации. 🙂