【问题标题】:State Pattern share common knowledge between state object efficient and extandable implementation状态模式在状态对象高效和可扩展实现之间共享常识
【发布时间】:2016-01-26 08:44:27
【问题描述】:

我正在使用状态模式,但我发现的示例是出于教育目的,我想知道在这种模式中在状态之间共享对象的最佳实践是什么,即布尔值、列表并且还为一个状态对象更改自动机对象中的状态引用

我将建立一个简单的模型作为示例。

public abstract class State {

    Automata automata;

    public State( Automata automata){
         this.automata = automata;
    }

    public abstract void action();

}

public class State1 extends State {

    public State1(Automata automata){
         super(automata)
    }

    @Override
    public void action() {
        // GET  : Use obj1 
        // POST :Set in automata state to State2 
    }
} 

public class State2 extends State {

     public State2(Automata automata){
         super(automata)
    }

    @Override
    public void action() {
        // GET  :Use obj1 
        // POST :Set in automata state to State1 
    }
}  

public class Automata {

     private State state1 = new State1(this);
     private State state2 = new State2(this);
     private State state = state1;

     public void takeAction() {
         state.action()
     }
}

我尝试了以下解决方案:

  1. 对于 GET 在 Automata 中存储 obj1 并使用 getter 和 setter。 POST 在 Automata 中存储状态并使用 getter 和 setter。这种方法会通过使用 getter 使代码变得不必要地长,并且随着 obj1 和状态的不断变化的列表变得难以维护。
  2. 使用私有内部类。将State, State1State2 声明为私有内部类并直接访问obj1 和状态。仅仅因为文件的长度,很难维护和更新。无法与其他 Automata 班级共享。
  3. 公开字段。我不想公开所有这些字段。
  4. 使用单例/静态类方法共享obj1's

我不太了解包私有访问。

在我的设计中,我将此模式与模板方法模式结合起来作为辅助信息。

我知道不存在一刀切的方法,但是使用这种模式的常见最佳实践是什么?

【问题讨论】:

  • 在状态之间共享对象是什么意思?状态应该是互斥的,它们的生命周期不应该重叠。您可以做的一件事是在从状态 A 转换到状态 B 时传输一个对象,但仅此而已。
  • 任何需要考虑过去事件/动作历史的设计,所有状态都应该能够读取和写入历史。我并不是说你的方法是错误的,只是如果总历史由 10 个对象组成,并且任何两个状态 si 和 sj 将具有固定数量的对象的交集(即 s1 使用 obj1 和 obj2 s2 使用 obj2 和 obj3 ... .s10 使用 obj10 和 obj1),您需要传递一长串参数,这会使代码不可读。此外,它不能解决 s1 想要转换到状态 s2 以将自动机的状态更新为正确的参考
  • 另外,正如@rinde 正确指出的那样,最好的选择不是让状态改变它的主机,而是让它返回一个新的状态。然后宿主通过自身变异转换到这个新状态。
  • TBH,没有朋友声明和内部类(为什么不呢?),package protected 字段似乎是将共享数据公开给相关状态的最短方法。也许将状态机放在专用包中。
  • @MarkusKull 这与全局变量无关。它是关于每个对象保护和隐藏自己的内部状态——基本 OO 原则之一。状态模式是一个很好的机会,可以为此添加一个功能性的扭曲并从不变性中受益,因为原则上您只需将State 替换为另一个,您不会在State 内改变事物。在多个 State 对象共享可变字段的情况下,用糟糕的 OO 来污染这一点会使 IMO 完全失败。

标签: java design-patterns state-pattern


【解决方案1】:

我会这样做:

public class Automata {
  List<String> someList;
  boolean someBoolean;
  private State currentState;

  public void performAction() {
    currentState = currentState.action(this);
  }

  interface State {
    State action(Automata context);
  }

  enum States implements State {
    IDLE {
      @Override
      public State action(Automata context) {
        if (context.someBoolean) {
          return WORKING;
        }
        return this;
      }
    },
    WORKING {
      @Override
      public State action(Automata context) {
        if (context.someList.size() > 7) {
          return IDLE;
        }
        return this;
      }
    }
  }
}

状态的设置现在在AutomataperformAction() 中完成,无需在每个状态中执行此操作。我使用 enum 作为状态的实现,因为当你想将状态实现为纯函数时它们很棒。但是,如果您的状态本身不是无状态的,您可能希望将状态实现为静态内部类。

【讨论】:

  • 我喜欢您编写的代码,但如果将状态移到 Automata 类之外,它基本上会在我的列表中碰到相同的 1 墙。如果状态需要一个变量并且您使用内部类,那么它是我列表中的选项 2。然而,*在动作中传递上下文是一个更好的选择,正如你所指出的那样,通过构造函数。上下文保持对共享状态的引用是否是一个好主意或可能,该共享状态被实现为 State 中的单例内部类,从而访问所有字段?
  • 只要将状态保存在与 Automata 相同的包中,它就可以正常工作。但是,如果您将 Automata 定义为一个接口,其中包含一些用于读取某些变量的特定方法和一些用于执行操作的方法,它可能会更简洁。然后你可以让 ConcreteAutomata 和 SpecialConcreteAutomata 都使用相同的状态,但行为可能不同(状态可以放在任何包中)。
  • 因此,如果SharedState(或AutomataBaseClass)和State,State1,State2在同一个包中,并且您使用包私有声明字段,那么您可以具有与我的答案相同的行为,但移动类SharedState 之外的类State 进入包?
  • 相同的行为,也许是的,但您的问题是关于状态机的设计,在我看来这有很大不同。
【解决方案2】:

在@rinde 发布之后,我很好奇这是否是一个可接受的解决方案

public abstract class State {

    public State(){
    }

    public abstract void action(sharedState);

    public class SharedState{
         Obj1 obj1;
         State state1;
         State state2;
         State curState;

         //Getters and setters
    }
}

public class State1 extends State {

    @Override
    public void action(SharedState sharedState) {
        // GET  : Use obj1 
        sharedState.obj1.doSomething()
        // POST :Set in automata state to State2 
        sharedState.curState = sharedState.state2;
    }
} 

//..same for State2

public class Automata {

   State.SharedState sharedState;

   public Automata(){
        sharedState.setState1(new State1());
        sharedState.setState2(new State2());
   }


   public void takeAction() {
       sharedState.getCurrentState().action(sharedState);
   }
}

【讨论】:

  • 我不知道你为什么认为你需要SharedState?这对我来说似乎是多余的。另外我认为最好只让Automata 自己改变当前状态(我在回答中将其设为私有)。
  • 在我的实际代码中,我选择了内部类,因为共享变量(知识)大约是 9 个,状态大约是 7 个,每个需要执行多达 4 个操作。这样,在状态动作中使用obj1.doSomething() 比使用getObj1().doSomething() 更简单。算法发生了很大变化,我想将它们移到外部并轻松管理它们,而无需使用 getter 和 setter 使操作中的代码复杂化。还要保留不同的实现。我对让我的生活更轻松的解决方案感兴趣:)
猜你喜欢
  • 1970-01-01
  • 2010-11-12
  • 2021-08-08
  • 2018-08-26
  • 2016-01-25
  • 1970-01-01
  • 1970-01-01
  • 2021-10-17
  • 1970-01-01
相关资源
最近更新 更多