【问题标题】:Decoupling States using the State Pattern使用状态模式解耦状态
【发布时间】:2020-03-23 09:18:28
【问题描述】:

对于我正在实施的特定状态模式,我不确定最佳 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这个词的用法,即。用另一个状态实例化new SomeState(),或在Controller 或另一个State 内实例化new SomeRequest()。在我看来,这会在 State 和它们的兄弟姐妹以及 Controller 和 States 之间产生高度耦合。

要求如下:

  1. 必须可以添加新状态,例如添加SniffingState。
  2. 还必须可以用新状态替换现有状态。例如,我应该能够将OffLeachState 替换为执行不同操作的不同OffLeashState。例如(由于某种原因代码不会格式化):

    公共类 OffLeachState2 扩展 DogState {
    公共无效seeCat(猫猫){
    if (dog.knows(cat)) {
    // 狗变成“PlayWithCatState”
    // cat 得到一个“PlayWithDog”请求
    } 其他 {
    // 狗变为“ChaseAnimalState”
    }
    }
    }

  3. 最后,必须记录 World 类中的所有更改。这意味着 World 类有一个记录器,可以跟踪正在发生的一切。这也是因为 World 类是一个模型,并且必须触发 notifyObservers() 以便视图知道要做什么。

我的问题是,状态、请求等应该存储在哪里?例如:

  1. Dog 中是否应该有状态“getter”?例如,dog.getBarkingState()、dog.getOnLeashState() 等?这似乎是有道理的,但它不会使Dog 类抗拒改变。即,每次我添加一个新的DogState 类时,我还必须确保Dog 有一个getter。此外,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对象作为参数传递。

非常感谢您的宝贵时间!

【问题讨论】:

    标签: java design-patterns structure state-pattern


    【解决方案1】:

    1.) 这看起来像是一道编程考试题。在这种情况下,如果不确定该怎么做,使用模式!因此,每个 State 都应该由 StateFactory 生成,并为 Factory 实例提供有关 World 的一些信息,以便它可以决定创建哪个特定的 State 实例。

    这是日志内容:

    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 知道其中发生的一切,你可以用消息替换状态设置器,让 World 实例监听每个人的状态变化。

    【讨论】:

    • 非常感谢您的及时回复!您介意包含一些代码作为示例吗?对于 1,我的理解是它会涉及到“dogStateFactory.createBarkingState()”之类的东西?对于 2,你会在设置新状态后输入 'notifyWorld()' 吗?
    • 好的。让我们看看我的晚餐受损的大脑吐出了什么......要在上面编辑我的答案。
    • 谢谢!哦,也许我应该开始写编程考试了。 :) 我想出了整个场景来隔离我在更大范围内遇到的问题。
    • 哇,非常感谢您在示例中所做的所有工作!你应该写一本教科书什么的哈哈。这些肯定会给我一些关于我应该做什么的想法。再次感谢!
    • 一旦我有足够的声望点,我一定会尽快给你答案。 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-02-01
    • 1970-01-01
    • 2017-01-05
    相关资源
    最近更新 更多