有一个糖果公司需要设计一个糖果售卖机,控制流程如下图,需要怎么实现?
这是一个状态图,每个圆圈都是一种状态。很明显,有、、、四个状态,同时也对应四个动作:,,和。那如何从状态图得到真正的代码呢?简单代码实现如下:
#! -*- coding: utf-8 -*-
classGumballMachine:
# 找出所有状态,并创建实例变量来持有当前状态,然后定义状态的值
STATE_SOLD_OUT=
STATE_NO_QUARTER=1
STATE_HAS_QUARTER=2
STATE_SOLD=3
state=STATE_SOLD_OUT
def__init__(self,count=):
self.count=count
ifcount>:
self.state=self.STATE_NO_QUARTER
def__str__(self):
return"Gumball machine current state: %s"%self.state
definsert_quarter(self):
# 投入25分钱
ifself.state==self.STATE_HAS_QUARTER:# 如果已经投过
print("You can't insert another quarter")
elifself.state==self.STATE_NO_QUARTER:# 如果没有投过
self.state=self.STATE_HAS_QUARTER
print("You inserted a quarter")
elifself.state==self.STATE_SOLD_OUT:# 如果已经售罄
print("You can't insert a quarter, the machine is sold out")
elifself.state==self.STATE_SOLD:# 如果刚刚买了糖果
print("Please wait, we're already giving you a gumball")
defeject_quarter(self):
# 退回25分
ifself.state==self.STATE_HAS_QUARTER:
print("Quarter returned")
self.state=self.STATE_NO_QUARTER
elifself.state==self.STATE_NO_QUARTER:
print("You haven't inserted a quarter")
elifself.state==self.STATE_SOLD:
print("Sorry, you alread turned the crank")
elifself.state==self.SOLD_OUT:
print("You can't eject, you haven't inserted")
defturn_crank(self):
# 转动曲柄
ifself.state==self.STATE_SOLD:
print("Turning twice doesn't get you another gumball")
elifself.state==self.STATE_NO_QUARTER:
print("You turned but there's no quarter")
elifself.state==self.STATE_SOLD_OUT:
print("You turned, but there are no gumballs")
elifself.state==self.STATE_HAS_QUARTER:
print("You turned...")
self.state=self.STATE_SOLD
self.dispense()
defdispense(self):
# 发放糖果
ifself.state==self.STATE_SOLD:
print("A gumball comes rolling out the slot")
self.count-=1
ifself.count==:
self.state=self.STATE_SOLD_OUT
else:
self.state=self.STATE_NO_QUARTER
elifself.state==self.STATE_NO_QUARTER:
print("You need to pay first")
elifself.state==self.STATE_SOLD_OUT:
print("No gumball dispensed")
elifself.state==self.STATE_HAS_QUARTER:
print("No gumball dispensed")
if__name__=="__main__":
# 以下是代码测试
gumball_machine=GumballMachine(5)# 装入5 个糖果
print(gumball_machine)
gumball_machine.insert_quarter()# 投入25分钱
gumball_machine.turn_crank()# 转动曲柄
print(gumball_machine)
gumball_machine.insert_quarter()#投入25分钱
gumball_machine.eject_quarter()# 退钱
gumball_machine.turn_crank()# 转动曲柄
print(gumball_machine)
gumball_machine.insert_quarter()# 投入25分钱
gumball_machine.turn_crank()# 转动曲柄
gumball_machine.insert_quarter()# 投入25分钱
gumball_machine.turn_crank()# 转动曲柄
gumball_machine.eject_quarter()# 退钱
print(gumball_machine)
这段代码有几个问题:
没有遵守开放-关闭原则
更像是面向过程的设计
状态转化被埋藏在条件语句中
未来加入新的需求,需要改动的较多,不易维护,可能会出bug
如何改进呢?考虑封装变化,把每个状态的行为都放在各自的类中,每个状态只要实现自己的动作,用加入新类的方式来实现新状态的加入。
定义State 父类,在这个类中,糖果机的每个动作都有一个应对的方法
为机器中的每个状态实现状态类,这些类将负责在对应的状态下进行机器的行为
摆脱旧的条件代码,将动作委托到状态类
新的实现代码如下:
#! -*- coding: utf-8 -*-
classState:
# 定义state基类
definsert_quarter(self):
pass
defeject_quarter(self):
pass
defturn_crank(self):
pass
defdispense(self):
pass
classSoldOutState(State):
# 继承State 类
def__init__(self,gumball_machine):
self.gumball_machine=gumball_machine
def__str__(self):
return"sold_out"
definsert_quarter(self):
print("You can't insert a quarter, the machine is sold out")
defeject_quarter(self):
print("You can't eject, you haven't inserted a quarter yet")
defturn_crank(self):
print("You turned, but ther are no gumballs")
defdispense(self):
print("No gumball dispensed")
classSoldState(State):
# 继承State 类
def__init__(self,gumball_machine):
self.gumball_machine=gumball_machine
def__str__(self):
return"sold"
definsert_quarter(self):
print("Please wait, we're already giving you a gumball")
defeject_quarter(self):
print("Sorry, you already turned the crank")
defturn_crank(self):
print("Turning twice doesn't get you another gumball")
defdispense(self):
self.gumball_machine.release_ball()
ifgumball_machine.count>:
self.gumball_machine.state=self.gumball_machine.no_quarter_state
else:
print("Oops, out of gumballs!")
self.gumball_machine.state=self.gumball_machine.soldout_state
classNoQuarterState(State):
# 继承State 类
def__init__(self,gumball_machine):
self.gumball_machine=gumball_machine
def__str__(self):
return"no_quarter"
definsert_quarter(self):
# 投币 并且改变状态
print("You inserted a quarter")
self.gumball_machine.state=self.gumball_machine.has_quarter_state
defeject_quarter(self):
print("You haven't insert a quarter")
defturn_crank(self):
print("You turned, but there's no quarter")
defdispense(self):
print("You need to pay first")
classHasQuarterState(State):
# 继承State 类
def__init__(self,gumball_machine):
self.gumball_machine=gumball_machine
def__str__(self):
return"has_quarter"
definsert_quarter(self):
print("You can't insert another quarter")
defeject_quarter(self):
print("Quarter returned")
self.gumball_machine.state=self.gumball_machine.no_quarter_state
defturn_crank(self):
print("You turned...")
self.gumball_machine.state=self.gumball_machine.sold_state
defdispense(self):
print("No gumball dispensed")
classGumballMachine:
def__init__(self,count=):
self.count=count
# 找出所有状态,并创建实例变量来持有当前状态,然后定义状态的值
self.soldout_state=SoldOutState(self)
self.no_quarter_state=NoQuarterState(self)
self.has_quarter_state=HasQuarterState(self)
self.sold_state=SoldState(self)
ifcount>:
self.state=self.no_quarter_state
else:
self.state=self.soldout_state
def__str__(self):
return">>> Gumball machine current state: %s"%self.state
definsert_quarter(self):
# 投入25分钱
self.state.insert_quarter()
defeject_quarter(self):
# 退回25分
self.state.eject_quarter()
# print("state", self.state, type(self.state))
defturn_crank(self):
# 转动曲柄
# print("state", self.state, type(self.state))
self.state.turn_crank()
defrelease_ball(self):
# 发放糖果
print("A gumball comes rolling out the slot...")
ifself.count>:
self.count-=1
if__name__=="__main__":
# 以下是代码测试
gumball_machine=GumballMachine(5)# 装入5 个糖果
print(gumball_machine)
gumball_machine.insert_quarter()# 投入25分钱
gumball_machine.turn_crank()# 转动曲柄
print(gumball_machine)
gumball_machine.insert_quarter()#投入25分钱
gumball_machine.eject_quarter()# 退钱
gumball_machine.turn_crank()# 转动曲柄
print(gumball_machine)
gumball_machine.insert_quarter()# 投入25分钱
gumball_machine.turn_crank()# 转动曲柄
gumball_machine.insert_quarter()# 投入25分钱
gumball_machine.turn_crank()# 转动曲柄
gumball_machine.eject_quarter()# 退钱
print(gumball_machine)
重构后的代码相对于之前的代码做了哪些事情呢?
将每个状态的行为局部话到自己的类中
删除if 语句
将对修改关闭,对糖果季类对
下图是刚初始状态图示:
上面重构部分代码使用的就是状态模式:
定义
: 状态模式允许对象在内部状态改变时改变它的行为,对象看起来好像修改了它的类。状态模式的类图如下:
状态模式是将多个行为封装在状态对象中, context 的行为随时可委托到其中一个状态中。当前状态在不同的状态对象中改变,以反映出context 内部的状态,context 的行为也会随之改变。
扩展
如果,现在要在这四个状态的基础上再加一个状态(购买糖果后,有10%的概率再得一个),该如何实现呢?
# 添加WinnerState 类,只有dispense 方法不同,可以从SoldState 类继承
classWinnerState(SoldState):
def__str__(self):
return"winner"
defdispense(self):
print("You're a WINNER! You get two gumballs for your quarter")
self.gumball_machine.release_ball()
ifgumball_machine.count==:
self.gumball_machine.state=self.gumball_machine.soldout_state
else:
self.gumball_machine.release_ball()
ifgumball_machine.count>:
self.gumball_machine.state=self.gumball_machine.no_quarter_state
else:
print("Oops, out of gumballs!")
self.gumball_machine.state=self.gumball_machine.soldout_state
# 修改turn_crank 方法
classHasQuarterState(State):
...
defturn_crank(self):
print("You turned...")
winner=random.randint(,9)
ifwinner==4andself.gumball_machine.count>1:# 如果库存大于 1 并且随机数等于4(可以是0到9任意值)
self.gumball_machine.state=self.gumball_machine.winner_state
else:
self.gumball_machine.state=self.gumball_machine.sold_state
# 在 GumballMachine 中初始化
classGumballMachine:
def__init__(self,count=):
self.count=count
# 找出所有状态,并创建实例变量来持有当前状态,然后定义状态的值
...
self.winner_state=WinnerState(self)
...
总结
状态模式允许一个对象给予内部状态而拥有不同的行为
状态模式用类代表状态
Context 会将行为委托给当前状态对象
通过将每状态封装进一个类,把改变局部化
状态装欢可以由State 类或Context 类控制
使用状态模式会增加类的数目
状态类可以被多个Context 实例共享
元旦快乐!
本文例子来自《Head First 设计模式》。最后,感谢女朋友支持和包容,比❤️也可以在公号输入以下关键字获取历史文章:||
领取专属 10元无门槛券
私享最新 技术干货