State(状态)模式

对象的状态是其属性当前值的一个组合.可以通过调用set-方法或者给对象字段分配值等方式来改变对象的改变。当我们调用对象的某个方法的时候,这个对象的状态也可能发生改变。对象通常在执行其方法时更改自身状态。

 

   在某些情况下,我们使用单词状态(state)来代表某对象的单个可变化属性。比如,我们也许说某机器的状态表述成开机(up)或者关机(down)。在这种情况下,对象状态中可变化部分也许是其行为最重要的方面。最后,依赖于对象状态的处理逻辑也许存在于很多其它类的方法中。其中可能有很多处理逻辑是相近或者类似的,这样会带来沉重的维护负担。

 

   解决这种状态相关处理逻辑分散状况的一种方式是引入新的类组,每个类表示一个不同的状态。然后,把状态相关的处理逻辑放在这些类

 

   State模式的意图在于将与状态有关的处理逻辑分散到代表对象状态的各个类中

 

1.状态建模

   如果一个对象的状态非常重要,那么当我们对这样的一个对象进行建模时,通常会利用一个变量来跟踪对象的行为。该变量可能会出现在复杂的、层叠嵌套的if语句块中。利用这个if语句块,我们可以对对象收到 的事件进行判断处理。使用这种方法来模拟状态存在的问题之一是if语句块会变得非常复杂;该方法存在的另一个问题是当我们对状态模型做调整时,通常不得不对多个方法中的if语句进行调整。而State模式则通过使用一个分布的操作作为解决此类问题提供了一种更为简洁的方法。借助于State模式的,可以把状态模拟为对象,在独立的类中封装其与状态相关的处理逻辑。为了解State模式的工作方式,应该先看看不用State模式情况下系统对状态的建模方式。接下来,我们会重构这部分代码,研究State模式是否能够改进代码设计。

 

  下面我们讨论Oozinoz公司对传送带的入口状态进行建模的软件。传送带设备是一个大型的,智能化的传送带。它可以通过入口接受原料,并能够根据原料材料箱上的条形码来分类存放原料。传送带的入口通过一个按钮来进行控制。当传送带的入口处于关闭状态时,按一下这个按钮,就可以将入口打开。如果在入口还没有完全打开之前,我们又按了一下这个按钮,这个时候入口将开始关闭。如果这个入口处于完全开户状态,那么在2秒钟后,它将会因为超时而自动开始关闭。我们可以在这个入口开启的时候,再按一下它的按钮,从而可以避免入口在两秒超时后自动关闭。图1显示了该传送带入口的状态及其变迁过程。


传送带入口提供一个按钮来改变入口的状态,用户可以通过该按钮控制传送带入口的状态

 

 突破题:假如我们打开了传送带的入口,并在入口处放置了一个材料箱。请问有什么方法可以不用等到超时就让入口关闭?

答:正如状态机所示,当入口打开的时候,我们单击按钮,这个入口将进入StayOpen状态;如果我们再次单击按钮,这个入口将会关闭。

 

  我们可以提供一个Door对象,随着传送带的状态变化,传送带软件能修改该对象。如图2:


Door类对传送带入口进行建模,这个类依赖于传送带设备发出的状态改变事件

 

Door类是Observable的一个子类,借助于这个类,客户端(比如GUI界面)可以查看入口的状态。这个类定义了一个入口所有可能到达的状态,具体代码如下:

package com.oozinoz.carousel;
import java.util.Observable;

public class Door extends Observable
{
     public final int CLOSED = -1;
     public final int OPENING = -2;
     public final int OPEN = -3;
     public final int CLOSING = -4;
     public final int STAYOPEN = -5;

     private int state = CLOSED;

     //......
}

(如果使用Java 5,你也许会选择使用枚举类型。)显而易见,入口状态的文本描述依赖于入口的状态:

 

public String status()
{
     switch(state){
         case OPENING:
               return "Opening";
         case OPEN;
               return "Open";
         case CLOSING:
               return "Closing";
         case STAYOPEN:
               return "StayOpen";
         default:
               return "Closed";
     }
}

当用户单击传送带的"一键式"按钮时,传送带将调用Door对象的touch()方法。Door类中的代码将实现图2所示的状态迁移:

public void touch()
{
     switch(state)  {
          case OPENING:
          case STAYOPEN:
               setState(CLOSING);
               break;
          case CLOSING:
          case CLOSED:
               setState(OPENING);
               break;
          case OPEN:
               setState(STAYOPEN);
               break;
          default:
               throw new Error("can't happen");
     }
}

  

 Door类中的setState()方法将入口的状态变化通知给观察者对象:

private void setState(int state)
{
     this.state = state;
     setChanged();
     notifyObservers();
}

 

突破题:请实现Door类中的complete()方法和timeout()方法。

答: 

public void complete()
{
    if(state == OPENING)
       setState(OPEN);
    else if(state == CLOSING)
       setState(Closed);
}

public void timeout()
{
     setState(CLOSING);
}

 
2.重构为State模式

   由于state变量在整个Door类中都被使用,因而Door类中的代码显得有些复杂。另外,我们很难将状态迁移方法,特别是touch()方法,与图1所示的状态机进行比较。这个时候,state模式可以帮助我们简化上面的代码。为了在本例中应用state模式,我们需要将入口的每个状态定义为一个单独的类。如图3:


该图根据传送带入口的状态机将该入口的状态映射成不同的类

 

 

 

 

经过此图所示的重构,为入口的每个状态都创建一个特殊类。上述每个类都包含单击每种入口状态时对应的状态变化逻辑。比如,文件DoorClosed.java包含如下代码:

 

 

 

DoorClosed类的touch()方法会把入口的新状态通知每个Door2对象。DoorClosed构造器需要参数Door2对象。此处设计要求每个状态对象保持对Door2对象的引用,以便于状态对象可以把状态迁移通知入口。这种设计方法要求状态对象引用某个特定的Door对象,这就使得状态对象只能应用于单个入口。接下来的部分将讨论如何修改这种设计,以保证单个状态集可以满足任意数量入口的需求。当前设计要求创建Door2对象的同时要创建属于该入口的状态集。

DoorState类是一个抽象类,它要求其每个具体子类必须实现touch()方法。状态机中的每个状态都是通过touch()方法完成变迁; 从这点讲,DoorState类层次结构与状态机是一致的。DoorState类将其他会导致状态迁移的方法都移出了这个类,因而DoorState的子类可以重写这些方法,或是直接忽略其中无关的方法,具体代码如下:

 

package com.oozinoz.carousel;

public abstract class DoorState
{

   protected Door2 door;

   public abstract void touch();

   public void complete();
  
   public void timeout() {}

   public String status(){
      String s= getClass().getName();
      return s.substring(s.lastIndexOf('.')+1);
   }

   public DoorState(Door2 door){
      this.door = door;
   }
}

 

注意:status()方法对所有状态都起作用,并且比重构前的代码要简单得多了。

在新设计中,我们并没有改变Door2对象的角色,仍然使用Door2对象从传送带接收状态变化事件。不过现在Door2对象只是简单地将这些状态变化信息转发给当前的state对象:

package com.oozinoz.carousel;
import java.util.Observable;

public class Door2 extends Observable{
    // variable and constructor ...

    public void touch(){
        state.touch();
    }

    public void complete(){
        state.complete();
    }

    public void timeout(){
        state.timeout();  
   }
  
   public String status(){
         return state.status();
    }

   protected void setState(DoorState state){
         this.state = state;
         setChanged();
         notifyObservers();
   } 
}

 

  touch(),complete(),status()和timeout()方法体现了多态性在这种设计中的应用。每个方法都代表了一种状态迁移方式。在每种情况下,操作是固定的,但是接收者类(state类)可能不同。多态性的原则就是实际执行哪个方法不仅取决于操作的方法签名,还取决于操作的接收者类。当我们调用touch()方法时会发生什么呢?答案是执行结果依赖于传送带入口的状态。现在的代码仍然可以高效地执行状态迁移,但是由于使用了多态性,故而代码比以前简单多了。

 

  DoorState的子类使用了Door2类中的setState()方法。这些子类与图1中的状态机很好的对应起来了。例如,DoorOpen代码中的touch()方法和timeout()方法分别对状态机中的Open状态的两种变迁做了处理:

 

突破题:请实现DoorClosing.java。

答:Closing代码中应有完成后到达Closed状态和单击按钮时到达Opening状态两种迁移方式,代码如下:

package com.oozinoz.carousel;
public class DoorClosing extends DoorState{
    public DoorClosing(Door2 door){
      super(door); 
   }

   public void complete(){
      door.setState(door.CLOSED);
   }

   public void touch(){
      door.setState(door.OPENING);
   }
}

 新设计使得代码更加简单,不过我们可能仍然感觉有点不满意,因为Door类使用的“常量”其实是局部变量。

 

3.使状态成为常量

  State模式把具体状态的处理逻辑迁移到表示对象状态的类中。但是对于状态对象和中心对象之间的通信和依赖关系,State模式并没有说明如何管理。在以前的设计方案中,每个状态类的构造器都接收一个Door对象。状态对象保持该对象,并用其来修改入口的状态。这并不是说构造器一定是个非常糟糕的设计,但是实例化Door对象确实会导致DoorState对象集初始化方面的问题。你也许更愿意创建单个静态的DoorState对象集合,并要求Door对象管理来自于状态变更的所有变化。

  使得状态对象变成常量的一个方法是让状态类简单地标识下一个状态,让Door类来更新其state变量。在这种设计中,Door类的touch()方法负责更新state变量,如下代码所示:

public void touch(){
    state = state.touch();
}

请注意,Door类的touch()方法的返回类型是void。DoorState类的子类也会实现touch(),但是这些实现将返回DoorState值。比如,DoorOpen类的touch()方法代码如下所示:

public DoorState touch(){
   return DoorState.STAYOPEN;
}

   在这种设计思路中,DoorState对象不保留对Door对象的引用,所以该应用程序只需要每个DoorState对象的单个实例。

   使得DoorState对象变成常量的另外一个方法是在状态迁移时传递给中心的Door对象。你可以给complete(),timeout()和touch()等状态变更方法添加一个参数:Door对象。在无需引用的情况下,这些方法接收中心的Door对象,并更新其状态。

 

突破题:请完成图4所示的类图,把DoorState对象变成常量,并在状态迁移时传递Door对象。

答:图4最终结果如下所示:


本设计把DoorState对象变成常量。DoorState状态迁移方法会更新Door对象的状态,

Door对象作为这些方法的接收参数

 

      当应用State模式时,你可以随意决定如何处理状态变更的通信情况。这些状态类保留对中心对象的引用,该中心对象的状态已经建模。同样,你可以在状态迁移过程中传递这个对象。也可把状态子类作为纯粹信息接收者,它们可以决定下一个状态,但是不能更新中心对象。至于选择何种方法则取决于应用的具体领域或者个人爱好。

 

     如果状态被不同线程使用,请保证状态迁移方法同步,以防止两个纯种同时修改状态时发生冲突。

     State模式的作用是可以将任何特定状态的处理逻辑集中放入单个类中

 

4.小结

  一般来说,一个对象中各个实例变量的值决定了这个对象的当前状态。在某些情况下,对象的大多数属性一旦设置就不会变化;一个属性是动态变化的,并在类的算法逻辑中发挥重要作用。这个属性出庭代表整个对象的状态,甚至是命名的state。

 

   现实生活中有些实体的状态非常关键,诸如事务或者机器,当使用对象对这个实体建模时,经常会出现一个极其重要的状态变量。在这种情况下,依赖对象状态的处理逻辑也许会出现在很多方法中。通过把状态相关行为迁移到状态对象层次,就可以简化代码。这使得每个状态类都包含应用领域中一个状态的行为。这样一来,状态类便可直接对应到状态机的状态。

 

  为处理状态之间的迁移,可以让中心对象保留对状态集的引用。或者,在状态迁移调用中,可以传递状态正在改变的中心对象。也可让状态类成为信息提供者,只提示接下来的状态,而无需更新中心对象。不论怎样管理状态迁移,都可应用State模式将对象的不同状态用一个状态类集合来表示,并将操作分散在这个状态类的集合中,从而简化我们的代码。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值