状态模式 Java

5

我正在学习Java中的设计模式。

我正在查阅一些链接。我试图通过状态模式来设计一台洗衣机。

关于状态设计模式的实现,我有一个疑问。

public interface State {

   public void openLid();
   public void closeLid();
   public void start();
   public void stop();
   public void washing();
  } 

 public class Idle implements State{
 //implementing overidden methods
 .......

 }

 public class Washing implements State {
       //implementing overidden methods
       .......
  }


 public class WashingMachine {
   State state;

   public WashingMachine(State state) {
    this.state =  new Idle();
   }

  public State getState() {
    return state;
   }

   public void setState(State state) {
    this.state = state;
   }

 }

我想了解在从空闲状态到洗涤状态之间转换时,可以有两种实现方式,这是我在网上看到的:
1. WashingMachine类实现State接口,并根据某些条件从Idle状态切换到washing状态,反之亦然。
2. IdleWashing类具有WashingMachine作为成员变量。
请问有人能建议一下吗?我对实现部分有点困惑。

1
你的任何一个想法都不可行。 - Bálint
3个回答

6

在回答您的问题之前,我建议先回顾一下模式的概念,并对您的代码提出一些小的修改建议。

状态模式允许对象在其内部状态改变时更改其行为。

在您的情况下,空闲和清洗是很好的状态候选项,而洗衣机是承载状态对象的好选择。

不过,有三点需要注意:

1)状态提供的方法应该是一些操作,其实现根据对象所处的状态而异。

在您的声明中:

public interface WashingMachineState {    
   public void openLid();
   public void closeLid();
   public void start();
   public void stop();
   public void washing();
  } 

washing() 不是一个动作,而是一个状态。
从空闲变为洗涤状态的是 start() 动作。

在状态模式中,带有状态的对象称为上下文
在你的情况下,上下文是 WashingMachine

2) 在状态模式中,思想是上下文想要执行一些行为,行为会根据当前状态进行变化。
为了实现这一点,上下文将其处理委托给其当前状态实例。
它避免了上下文中有许多if-else if(对于每个处理),还使上下文的复杂性减小,因为当您使用状态模式时,可以获得行为家族::

  • 当我们处于空闲状态时的行为位于 IdleState 类中。

  • 当我们处于洗涤状态时的行为位于 WashingState 类中。

  • 等等...

要执行操作,状态实例需要上下文 (WashingMachine)。
针对这个问题,您有两种方式:

要么将 WashingMachine 对象存储为状态实例的字段,要么在上下文 WashingMachine 对象将处理委托给状态时将其作为参数传递。

我建议您使用无状态的方式。
因此,当在 WashingMachine 实例上调用 startWashing() 操作时,WashingMachine 实例应通过传递自己(例如 state.startWashing(this))将处理委托给 state.startWashing()
状态应该提供一个 WashingMachine 参数:

public interface WashingMachineState {    
   void openLid(WashingMachine machine);
   void closeLid(WashingMachine machine);
   void pushStartBtn(WashingMachine machine);
   void pushStopBtn(WashingMachine machine);
} 

3) 你实际上定义了两种状态:空闲和清洗。
这些状态应该增加一个 停止 状态,因为当洗衣机处于 "正在停止" 状态时,一些操作(例如打开门、按下启动按钮等)具有特定的行为。需要注意的是,仅使用两个状态,您可能还会想知道该模式是否相关。


现在我可以回答你的问题了。

我想知道从空闲到清洗之间切换状态的实现方式有两种,这是我在网上看到的:

1. WashingMachine 类实现 State 接口,并根据某些条件从空闲状态切换到清洗状态或反之亦然。

2. Idle 和 Washing 类使用 WashingMachine 作为成员变量。

WashingMachineWashingMachineState 是协作但不同的东西。
所以它们不应该依赖于相同的接口。
WashingMachine 对象作为状态子类的字段是一种可能性。
如前所述,您也可以将 WashingMachine 作为 State 方法的参数传递。

请注意,直接执行从一个状态到另一个状态的切换不是由 WashingMachine 执行的。
这是由状态执行的。
状态应该调用 WashingMachine.changeState() 来执行它。

WashingMachine 可以是:

public class WashingMachine {

   private WashingMachineState state;

   public WashingMachine() {
     this.state =  new Idle();
   }        

   protected void changeState(WashingMachineState state) {
     this.state = state;      
   }

   public void openLid(){
     state.openLid(this);
   } 

   public void closeLid(){
     state.closeLid(this);         
   } 
   public void pushStartBtn(){
     state.pushStartBtn(this);
   } 

   public void pushStopBtn(){
     state.pushStopBtn(this);
   } 

   public State getState() {
      return state;
   }

 }

关于WashingMachine的修改解释:

  • 当使用状态模式时,changeState更具有意义,可作为setState使用。

  • changeState(State)可以使用protected修饰符来减少该方法以及状态子类的可见性,当然状态子类应该与WashingMachine在同一个包中。这是Java中实现细节的一种方式。当然,在其他面向对象编程语言中,您也有其他选择。

关于从空闲状态切换到洗涤状态,我认为只有在IdleState状态下调用pushStartBtn()时才可能发生。

以下是示例:

public class IdleState implements State {    
   public void openLid(WashingMachine machine){
       ...
   }
   public void closeLid(WashingMachine machine){
       ...
   }
   public void pushStartBtn(WashingMachine machine){
      //do processing with machine to begin effectively the washing
         ...
      machine.changeState(new WashingState());
   }

   public void pushStopBtn(WashingMachine machine){
       ...
   }
 } 

@coder25 我修改了第三点。停止状态似乎比打开门状态更相关。打开门状态不是一级状态,那更像是子状态。没有太多的兴趣去表示它。 - davidxxx
关于更改状态(machine.changeState(new WashingState()))- 在上下文类中拥有所有状态并具有getter,每个状态都像 machine.changeState(machine.getWashingState()) 这样做以保持状态对象单例是否可接受? - Thiyagu
如果状态对象是无状态的(总结一下:状态类中没有实例字段),那么这是可能的,甚至是一个好主意。在这种情况下,我可能会使用枚举来表示状态,而不是使用getter方法:public enum WashingMachineState { WASHING,IDLE,STOPPING },因为它提供了单例模式,并且是更直接的方式。 - davidxxx
但是(作为一个人为的例子),如果它有一些依赖注入(可能是一些策略)作为实例变量,你会怎么做呢? - Thiyagu
我不明白。你可能混合了太多的东西 :) 如果你有一个有趣的问题,请毫不犹豫地提出问题,我相信会有人解答它 :) - davidxxx

1
我认为更好的选择是创建一个 <\p> 标签。
public enum State{IDLE,WASHING};

使用这个enum。这个enum可以被命名为WashingMachineState,因为你提到的状态只适用于洗衣机 - 它们不可重复使用,所以这里没有接口的意义。

如果您想在不同的设备之间共享相同的状态,例如WashingMachineDishWasher,那么您可以使用

public interface Statefull{
   public State getState();
   public void changeState(State newState);
}

WashingMachineDishWasher实现Statefull接口。 使用Java 8的默认接口实现,您甚至可以在接口中包含getter实现,因此在实现类中没有样板代码。


我正在阅读这个链接:http://www.superglobals.net/java-state-design-pattern/,它定义了状态模式。 - coder25
我不会将它扩展到洗碗机上。我想学习如何使用状态模式。 - coder25

0

好的,让我们来看看你们两个的想法:

1.) 洗衣机是状态吗?不,绝对不是。那么为什么它需要实现状态接口呢?

2.) 这个方法几乎可以,但它使得状态不可重用(对于这样一个小的实现来说还不错,但对于像游戏这样的东西来说,就很糟糕了)。它们只能是洗衣机的状态。如果我想要将这些状态用于洗碗机怎么办?

你应该做的:

创建一个接口或类,比如StateManager,并让WashingMachine实现它,然后不使用具体的类,在状态中创建一个StateManager字段。

例如:

public abstract class StateManager {
    public State state;

    public void setState(State newState) {
        state = newState;
        newState.parent = this;
    }
    public State getState() {
        return state;
    }
}

状态:

public abstract class State {
    public StateManager parent;
    // Whenever you want to set the object's state, use this
}

示例状态:

public class WashingState extends State {
    // your methods here
}

示例状态管理器:

public class WashingMachine extends StateManager {
    // your methods here.
}

我将所有的接口都改成了类,因为它们可以更容易地创建一些对象(比如如果你想制造洗碗机和洗衣机,那么你可以将洗涤部分提取出来,并为其制作一个单独的类)。


你能举个例子来解释一下吗? - coder25
我到家后会发布一些东西。 - Bálint
你能否发布一些关于你的设计建议的内容? - coder25
完成,这是翻译好的文本: - Bálint

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接