文章内容参考自《Head First设计模式》的状态模式这一节。
一、state模式
- 定义: 状态模式允许对象在内部状态改变时改变他的行为,对象看起来好像修改了他的类。
- 主要解决:对象的行为依赖于它的状态(属性),并且可以根据它的状态改变而改变它的相关行为。
- 何时使用:代码中包含大量与对象状态有关的条件语句。
二、模式类图
三、案例
某糖果公司想要发明一个万能糖果机。控制器的工作流程需要如下图所示:
这张图是一个状态图,每一个圆圈代表一个状态,没有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%的几率掉下来的是两颗糖果(多送你一颗)
新的需求出现,幸亏当时我们使用了状态模式,使这一问题变得很容易。我们先来看一下新的状态图。
可以看出新的状态图增加了一个新的状态: 赢家,那我们只要新增加一个 赢家状态类就可以实现这一需求。
- 新增一个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中。