状态模式

45 阅读7分钟

状态

状态指事物基于所处的状况、形态表现出的不同的行为特性。状态模式(State)构架出一套完备的事物内部状态转换机制,并将内部状态包裹起来且对外部不可见,使其行为能随其状态的改变而改变,同时简化了事物的复杂的状态变化逻辑。

面向对象最基本的特性——“封装”是对现实世界中事物的模拟,类封装了属性与方法,其被实例化后的对象属性则体现出某种状态,以至调用其方法时会展现出某种相应的行为,这一切都与状态脱不了干系。以我们赖以生存的水举例,它有3种形态,0℃以下的固态冰、常温下的液态水,以及100℃以上的气态水蒸气。我们可以总结出,当温度变化导致水的状态发生变化时,它就会有不同的行为,如冰会滚动、水会流动、水蒸气则会漂浮。

交通灯的状态

交通信号灯为例,它一般包括红、黄、绿3种颜色状态,不同状态之间的切换包含这样的逻辑:红灯只能切换为黄灯,黄灯可以切换为绿灯或红灯,绿灯只能切换为黄灯

交通灯的状态维护与切换并不像电灯一样简单,如果复杂的状态校验逻辑会大量堆积在每个方法中,因此造成的错误必将导致严重的交通事故,后果不堪设想。

这个交通灯状态切换逻辑看起来非常复杂,满当当地摆放在类里面,维护起来也非常让人头疼。这只是十字路口的一处交通灯而已,若是东西南北各处交通灯全部联动起来的话,其复杂程度难以想象。要解决这个问题,我们就得基于状态模式,将这个庞大的类进行拆分,用一种更为优雅的方式将这些切换逻辑组织起来,让状态的切换及维护变得轻松自如。沿着这个思路,我们把状态相关模块从交通灯里抽离出来,这里首先定义一个状态接口以形成规范

package State;

public interface State {
   void switchToGreen(TrafficLight trafficLight);//切换为绿灯
   void switchToYellow(TrafficLight trafficLight);//切换为黄灯
   void switchToRed(TrafficLight trafficLight);//切换为红灯
}

状态接口分别定义3个标准,它们依次是切换为绿灯(通行)状态、切换为黄灯(警示)状态,以及切换为红灯(禁行)状态。需要注意的是每个接口方法的入参,这里传入的交通灯引用到底有何用意?我们先保留这个问题。状态接口既然已经定义完毕,那么接着就得实现交通灯的3种状态

package State;

public class Red implements State {
   @Override
   public void switchToGreen(TrafficLight trafficLight) {
      System.out.println("ERROR,红灯不能切换为绿灯");
   }

   @Override
   public void switchToYellow(TrafficLight trafficLight) {
      trafficLight.setState(new Yellow());
      System.out.println("OK,黄灯亮起5秒");
   }

   @Override
   public void switchToRed(TrafficLight trafficLight) {
      System.out.println("ERROR!!!,已是红灯状态无需再切换");
   }
}
package State;

public class Green implements State {
   @Override
   public void switchToGreen(TrafficLight trafficLight) {
      System.out.println("ERROR!!!已是绿灯状态无须再切换");
   }

   @Override
   public void switchToYellow(TrafficLight trafficLight) {
      trafficLight.setState(new Yellow());
      System.out.println("OK......黄灯亮5s");
   }

   @Override
   public void switchToRed(TrafficLight trafficLight) {
      System.out.println("ERROR!!!绿灯不能切换为红灯");
   }
}
package State;

public class Yellow implements State {
   @Override
   public void switchToGreen(TrafficLight trafficLight) {
      trafficLight.setState(new Green());
      System.out.println("绿灯亮起60s");
   }

   @Override
   public void switchToYellow(TrafficLight trafficLight) {
      System.out.println("ERROR!!!!,已是黄灯无须再切换");
   }

   @Override
   public void switchToRed(TrafficLight trafficLight) {
      trafficLight.setState(new Red());
      System.out.println("OK.....红灯亮起60s");
   }
}

通过对交通灯系统的初步重构,我们将“状态”接口化、模块化,最终将它们从臃肿的交通灯类代码中抽离出来,独立于交通灯类,并分别拥有自己的接口实现。如此一来,我们奇迹般地摆脱了各种复杂的状态切换逻辑,代码变得特别清爽、优雅。至于状态接口中传入的交通灯对象以及对其状态更新的setState()方法,读者可能会感到困惑,我们先来重构交通灯类,让一切豁然开朗

package State;

public class TrafficLight {
   State state = new Red();

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

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

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

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

为了让状态对象能够访问到setState()更新交通灯的状态,我们将交通灯对象“this”作为参数一并传入,将任务移交给当前的状态对象去执行,也就是说,交通灯只是持有当前的状态,至于到底该如何响应及进行状态切换,全权交由当前状态对象处理。至此,基于状态模式的交通灯系统构建完毕,我们来定义客户端类使用交通灯

客户端类Client

package State;

public class Client {
   public static void main(String[] args) {
      TrafficLight trafficLight = new TrafficLight();
      trafficLight.switchToYellow();;
      trafficLight.switchToGreen();
      trafficLight.switchToYellow();
      trafficLight.switchToRed();
   }
}

总结:状态响应机制

至此,状态模式的应用将系统状态从系统环境(系统宿主)中彻底抽离出来,状态接口确立了高层统一规范,使状态响应机制分立、自治,以一种松耦合的方式实现了系统状态与行为的联动机制。如此一来,系统环境不再处理任何状态响应及切换逻辑,而是转发给当前状态对象去处理,同时将自身引用“this” 传递下去。也就是说,系统环境只需要持有当前状态,而不必再关心如何根据状态进行响应,或是如何进行状态更新了。请参看状态模式的类结构

State(状态接口):定义通用的状态规范标准,其中处理请求方法handle()将系统环境Context作为参数传入。对应本章例程中的状态接口State。

ConcreteStateA、ConcreteStateB、ConcreteStateC(状态实现A、状态实现B、状态实现C):具体的状态实现类,根据系统环境用于表达系统环境Context的各个状态,它们都要符合状态接口的规范。对应本章例程中的红灯状态Red、绿灯状态Green以及黄灯状态Yellow。

Context(系统环境):系统的环境,持有状态接口的引用,以及更新状态方法setState(),对外暴露请求发起方法request(),对应本章例程中的交通灯类TrafficLight。

从类结构上看,状态模式与策略模式非常类似,其不同之处在于,策略模式是将策略算法抽离出来并由外部注入,从而引发不同的系统行为,其可扩展性更好;而状态模式则将状态及其行为响应机制抽离出来,这能让系统状态与行为响应有更好的逻辑控制能力,并且实现系统状态主动式的自我转换。状态模式与策略模式的侧重点不同,所以适用于不同的场景。总之,如果系统中堆积着大量的状态判断语句,那么就可以考虑应用状态模式,它能让系统原本复杂的状态响应及维护逻辑变得异常简单。状态的解耦与分立让代码看起来更加清晰、明了,可读性大大增强,同时系统的运行效率与健壮性也能得到全面提升。

GO版本代码

package state

import "fmt"

type state interface {
    switchToGreen(*trafficLight) //切换为绿灯
    swichToYellow(*trafficLight) //切换为黄灯
    switchToRed(*trafficLight)   //切换为红灯
}
type red struct {
}

func (l *red) switchToRed(t *trafficLight) {
    fmt.Println("ERROR!!!!,已是红灯无须切换")
}

func (l *red) switchToGreen(t *trafficLight) {
    fmt.Println("ERROR,红灯不能切换为绿灯")
}

func (l *red) swichToYellow(t *trafficLight) {
    t.setState(&yellow{})
    fmt.Println("OK,黄灯亮起5秒")
}

type yellow struct {
}

func (l *yellow) swichToYellow(t *trafficLight) {
    fmt.Println("ERROR!!!,已是黄灯无须切换")
}
func (l *yellow) switchToGreen(t *trafficLight) {
    t.setState(&green{})
    fmt.Println("OK......绿灯亮起60s")
}
func (l *yellow) switchToRed(t *trafficLight) {
    t.setState(&red{})
    fmt.Println("OK......红灯亮起60s")
}

type green struct {
}

func (l *green) swichToYellow(t *trafficLight) {
    t.setState(&yellow{})
    fmt.Println("OK.....黄灯亮5s")
}
func (l *green) switchToRed(t *trafficLight) {
    fmt.Println("ERROR!!!绿灯不能切换为红灯")
}
func (l *green) switchToGreen(t *trafficLight) {
    fmt.Println("ERROR!!!已是绿灯无须切换")
}

type trafficLight struct {
    state
}

func NewTrafficLight() *trafficLight {
    return &trafficLight{
        state: &red{},
    }
}

func (t *trafficLight) setState(s state) {
    t.state = s
}
func (t *trafficLight) SwitchToRed() {
    t.switchToRed(t)
}
func (t *trafficLight) SwitchToGreen() {
    t.switchToGreen(t)
}
func (t *trafficLight) SwichToYellow() {
    t.swichToYellow(t)
}
package state_test

import (
    state "desginPatterns/State"
    "testing"
)

func TestState(t *testing.T) {
    trafficLight := state.NewTrafficLight()
    trafficLight.SwichToYellow()
    trafficLight.SwitchToGreen()
    trafficLight.SwichToYellow()
    trafficLight.SwitchToRed()
}