设计模式之state模式


文章内容参考自《Head First设计模式》的状态模式这一节。

一、state模式

  • 定义: 状态模式允许对象在内部状态改变时改变他的行为,对象看起来好像修改了他的类。
  • 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
  • 何时使用:代码中包含大量与对象状态有关的条件语句。

二、模式类图

设计模式之state模式_第1张图片
模式图

三、案例

某糖果公司想要发明一个万能糖果机。控制器的工作流程需要如下图所示:

设计模式之state模式_第2张图片
控制器工作流程

这张图是一个状态图,每一个圆圈代表一个状态,没有25分钱是初始状态,等着你把钱放进来。每一个状态都代表机器不同的配置以某种方式行动,需要某些动作将目前的状态转换到另一个状态。

四、用模式实现

从上图可以看出,如何只是普通的实现的话,代码中会包含大量的if else语句(因为每个动作都要进行状态的判断)。即使实现出来,那么如果再加入新的状态,代码修改起来会很困难。幸运的是我们有状态模式来帮助我们解决这一难题。

  • 首先我们要定义一个抽象类State,其拥有的方法直接映射到糖果机可能发生的动作。
    State.py
# coding=utf-8
from abc import ABCMeta, abstractmethod


class State(object):
    metaclass = ABCMeta

    def __init__(self, gumballMachine):
        """糖果机的引用,将他记录在实例变量中"""
        self.gumballMachine = gumballMachine

    @abstractmethod
    def insertQuarter(self):
        """投入25分钱的动作"""
        pass

    @abstractmethod
    def ejectQuarter(self):
        """退回25分钱的动作"""
        pass

    @abstractmethod
    def turnCrank(self):
        """转动曲柄"""
        pass

    @abstractmethod
    def dispense(self):
        """发放糖果"""
        pass

  • 对于有钱、没钱、售出糖果、告罄四个状态继承State状态
    NoQuarterState.py
# coding=utf-8
from State import State


class NoQuarterState(State):
    """state: 没有25分钱"""
    def __init__(self, gumballMachine):
        State.__init__(self, gumballMachine)

    def insertQuarter(self):
        print "You inserted a quarter"
        # 投入钱后,状态切换到有钱状态
        self.gumballMachine.setState(self.gumballMachine.getHasQuarterState())

    def ejectQuarter(self):
       print "You haven't inserted a quarter"

    def turnCrank(self):
       print "You turned, but there is no quarter"

    def dispense(self):
        print "You need to paly first"

HasQuarterState.py

# coding=utf-8
from State import State


class HasQuarterState(State):
    """state: 有25分钱"""
    def __init__(self, gumballMachine):
        State.__init__(self, gumballMachine)

    # 这是此状态一个不恰当的动作
    def insertQuarter(self):
        print "You can't insert another quarter"

    def ejectQuarter(self):
        print "Quarter returned"
        # 退出钱后,将状态切换到NoQuarterState状态
        self.gumballMachine.setState(self.gumballMachine.getNoQuarterState())

    def turnCrank(self):
        print "You turned...."
        # 转动后,切换状态为售出糖果状态。
        self.gumballMachine.setState(self.gumballMachine.getSoldState())

    # 另一个不恰当的动作
    def dispense(self):
        print "No gumball dispense"

SoldState.py

# coding=utf-8
from State import State


class SoldState(State):
    """state:售出糖果"""
    def __init__(self, gumballMachine):
        State.__init__(self, gumballMachine)

    # 对于此状态来说,以下三种动作都是不恰当的。
    def insertQuarter(self):
        print "Please wait, we're already giving you a gumball!"

    def ejectQuarter(self):
        print "Sorry, you already turned the crank!"

    def turnCrank(self):
        print "Turning twice doesn't get you another gumball!"

    # 根据工作流程图,此动作才是此状态真正的动作。
    def dispense(self):
        # 现在是SoldState状态,顾客已经付过钱,发放糖果。
        self.gumballMachine.releaseBall()

        # 检查糖果机的剩余数目,切换到不同的状态
        if self.gumballMachine.getCount() > 0:
            self.gumballMachine.setState(self.gumballMachine.getNoQuarterState())
        else:
            print "Oops, out of gumballs"
            self.gumballMachine.setState(self.gumballMachine.getSoldOutState())

SoldOutState.py

# coding=utf-8
from State import State


class SoldOutState(State):
    """state: 糖果告罄"""
    def __init__(self, gumballMachine):
        State.__init__(self, gumballMachine)

    def insertQuarter(self):
        print "the machine is sold out"

    def ejectQuarter(self):
        print "the machine is sold out"

    def turnCrank(self):
        print "the machine is sold out"

    def dispense(self):
        print "the machine is sold out"
  • 定义客户感兴趣的接口,GumballMachine类
    GumballMachine.py
# coding=utf-8
from HasQuarterState import HasQuarterState
from NoQuarterState import NoQuarterState
from SoldState import SoldState
from SoldOutState import SoldOutState


class GumballMachine(object):
    """糖果机"""
    def __init__(self, numberGumballs):  # 初始糖果数目
        # 每一种状态都创造一个状态实例
        self.soldOutState = SoldOutState(self)
        self.noQuarterState = NoQuarterState(self)
        self.hasQuarterState = HasQuarterState(self)
        self.soldState = SoldState(self)

        self.count = numberGumballs
        if self.count > 0:   # 如果糖果数目 > 0,就把状态设为NoQuarterState
            self.state = self.noQuarterState

        else:
            # 初始状态为SoldOutState
            self.state = self.soldOutState

    def insertQuarter(self):
        self.state.insertQuarter()

    def ejectQuarter(self):
        self.state.ejectQuarter()

    def turnCrank(self):
        self.state.turnCrank()
        # 他是一个内部动作,用户不允许直接要求机器发糖果,所以不用在此类中去实现它
        self.state.dispense()

    def setState(self, state):
        self.state = state

    def getHasQuarterState(self):
        """返回HasQuarterState的状态,供具体状态类调用进行状态转换"""
        return self.hasQuarterState

    def getNoQuarterState(self):
        return self.noQuarterState

    def getSoldState(self):
        return self.soldState

    def getSoldOutState(self):
        return self.soldOutState

    def releaseBall(self):
        """这个机器提供了一个releaseBall()的辅助函数来释放糖果,并将count减1"""
        print "A gumball comes rolling out the slot"
        if self.count != 0:
            self.count -= 1

    def getCount(self):
        return self.count
  • 测试必不可少
    main.py
# coding=utf-8

from GumballMachine import GumballMachine


if __name__ == '__main__':
    # 初始化糖果机糖果数目为5
    gumball_machine = GumballMachine(5)

    # 打印出机器的初始状态
    print gumball_machine.state

    # 投入一枚25分钱硬币,转动曲柄,拿到糖果
    gumball_machine.insertQuarter()
    gumball_machine.turnCrank()

    # 再次打印出机器的状态
    print gumball_machine.state

    # 投入一枚25分钱,要求机器退钱,转动曲柄
    gumball_machine.insertQuarter()
    gumball_machine.ejectQuarter()
    gumball_machine.turnCrank()

    # 再次打印出机器的状态
    print gumball_machine.state

    gumball_machine.insertQuarter()
    gumball_machine.turnCrank()
    gumball_machine.insertQuarter()
    gumball_machine.turnCrank()
    # 取出糖果后,要求机器退钱
    gumball_machine.ejectQuarter()

    # 再次打印出机器的状态
    print gumball_machine.state

    # 压力测试
    gumball_machine.insertQuarter()
    gumball_machine.insertQuarter()
    gumball_machine.turnCrank()
    gumball_machine.insertQuarter()
    gumball_machine.turnCrank()
    gumball_machine.ejectQuarter()

    # 再次打印出机器的状态
    print gumball_machine.state

可以看出我们的代码实现了以下几点:

  • 将每个状态的行为局部化到他自己的类中。
  • 将容易产生问题的if语句避免掉,以便日后的维护。
  • 让每个状态“对修改关闭”、让糖果机“对扩展开发”,因为可以加入新的状态类。
  • 容易理解和阅读。

五、需求变更

新的需求: 糖果公司认为将“购买糖果”变成是一个游戏,可以大大增加他们的销售量。当曲柄转动时,有10%的几率掉下来的是两颗糖果(多送你一颗)
新的需求出现,幸亏当时我们使用了状态模式,使这一问题变得很容易。我们先来看一下新的状态图。

设计模式之state模式_第3张图片
新的状态图

可以看出新的状态图增加了一个新的状态: 赢家,那我们只要新增加一个 赢家状态类就可以实现这一需求。

  • 新增一个WinnerState类,继承State类。

WinnerState.py

# coding=utf-8
from State import State


class WinnerState(State):
    """state:赢家"""
    def __init__(self, gumballMachine):
        State.__init__(self, gumballMachine)

        # 对于此状态来说,以下三种动作都是不恰当的。

    def insertQuarter(self):
        print "Please wait, we're already giving you a gumball!"

    def ejectQuarter(self):
        print "Sorry, you already turned the crank!"

    def turnCrank(self):
        print "Turning twice doesn't get you another gumball!"

    def dispense(self):
        print "You win, you get two gumball"
        # 发放糖果
        self.gumballMachine.releaseBall()

        if self.gumballMachine.getCount() == 0:
            self.gumballMachine.setState(self.gumballMachine.getSoldOutState())
        else:
            # 发放第二颗糖
            self.gumballMachine.releaseBall()

            if self.gumballMachine.getCount() > 0:
                self.gumballMachine.setState(self.gumballMachine.getNoQuarterState())
            else:
                print "Oops, out of gumballs"
                self.gumballMachine.setState(self.gumballMachine.getSoldOutState())

相对于SoldState只是dispense做了一点更改。但是我们还需要实现随机数,还要增加一个进入Winnerstate状态的转换。这两件事都要加入HasQuarterState,因为顾客会从这个状态转动曲柄。改变后的HasQuarterState为:

# coding=utf-8
from State import State
import random


class HasQuarterState(State):
    """state: 有25分钱"""
    def __init__(self, gumballMachine):
        State.__init__(self, gumballMachine)

    # 这是此状态一个不恰当的动作
    def insertQuarter(self):
        print "You can't insert another quarter"

    def ejectQuarter(self):
        print "Quarter returned"
        # 退出钱后,将状态切换到NoQuarterState状态
        self.gumballMachine.setState(self.gumballMachine.getNoQuarterState())

    def turnCrank(self):
        print "You turned...."
        winner = random.randint(0, 9)
        if (winner == 0) and (self.gumballMachine.getCount() > 1):
            # 赢了之后,转换状态为赢家
            self.gumballMachine.setState(self.gumballMachine.getWinnerState())
        else:
            # 转动后,切换状态为售出糖果状态。
            self.gumballMachine.setState(self.gumballMachine.getSoldState())

    # 另一个不恰当的动作
    def dispense(self):
        print "No gumball dispense"

当然GumballMachine中也要进行相应的改变,这里就不再赘述。

总结:

当遇到对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为时,我们选择状态模式。
首先我们找到内部的所有状态行为,把行为封装起来形成接口(State类),然后,每个具体的状态类(ConcreteState)去实现这个接口。当外部系统通过Context访问这些行为时,Context将行为委托到当前状态去处理。
另注:
状态装换可以由Context控制,也可以由State控制。一般来说,当状态是固定的时候,就适合放在Context中,然而,当转换是更动态的时候,通常会放在State中。

你可能感兴趣的:(设计模式之state模式)