Python Cookbook-6.14 实现状态设计模式
任务
你希望你程序中的某个对象能在不同的“状态”之间切换,而且该对象的行为方式也能随着状态的变化而变化。
解决方案
状态设计模式的关键思路是将“状态”(带有它自身的行为方式)对象化,使其成为一个类实例(带有一些方法)。在Python中,你不用创建一个抽象类来表现这些不同状态共同的接口:只需为这些“状态”本身编写不同的类即可。比如:
class TraceNormal(object):'正常的状态'def startMessage(self):self.nstr = self.characters = 0def emitString(self,s):self.nstr += 1self.characters += len(s)def endMessage(self):print '%d characters in %d strings' %(self.characters,self.nstr)
class TraceChatty(object):'详细的状态'def startMessage(self):self.msg = []def emitstring(self,s):self.msg.append(repr(s))def endMessage(self):print 'Message:', ','.join(self.msg)
class TraceQuiet(object):'无输出的状态'def startMessage(self):passdef emitString(self,s):passdef endMessage(self):pass
class Tracer(object):def __init__(self,state):self.state = statedef setState(self,state):self.state = statedef emitStrings(self,strings):self.state.startMessage()for s in strings:self.state.emitString(s)self.state.endMessaqe()
if __name__ == '__main__':t = Tracer(TraceNormal())t.emitStrings('some example strings here'.split())
#输出:21 characters in 4 stringst.setstate(TraceQuiet())t.emitStrings('some example strings here'.split())
#无输出t.setstate(TraceChatty())t.emitStrings('some example strings here'.split())
#输出:Message:'some','example','strings','here'
讨论
通过状态设计模式,我们能够“发掘出”一个对象的一些相关的行为(还可能包括一些与这些行为相关的数据),并将其置入一个辅助的状态对象,然后主对象可以在需要的时候通过调用“状态”对象的方法委托给这些行为。在Python 的术语中,这种设计模式和重新绑定整个对象的__class__惯用法有一些联系,详情请参看6.11节,和重新绑定某些方法的方式也有些关联(如2.14节所示)。从某种意义上说,状态设计模式是处于前两种概念之间的:你将一些关联的行为组织起来,而不是通过更改对象的__class__ 在这些行为之间切换,也不是修改每一个方法。如果用经典设计模式的术语来描述,本节提供的模式处于经典状态设计模式和经典策略设计模式之间。
与一些相关的 Python 概念相比,状态设计模式看上去更有魅力,因为适当数量的数据可以和你要委托的行为共生,而且数量正合适,正好可用于支持每种特别的行为。在本节解决方案的例子中,不同的状态对象需要截然不同的数据类型和不同数量的数据:对于类 TraceQuiet 而言,根本就无须任何数据,而 TraceNormal 只需要几个数字,TraceChatty 则需要所有的字符串列表。这些责任通常都被主对象委托给了各个“状态对象”。
不过有时会有一些超出我们的示例的情况,状态对象可能会需要更紧密地同主对象合作在某些环境下甚至还需要调用主对象的方法,访问主对象的属性。为了实现这个目的,主对象可以传递一个参数给“状态”对象,可以是self或者某个self的被绑定方法。比如,假设本节解决方案中的示例需要扩展功能,主对象必须记录它送出的消息已经输出了多少行。Tracer.__init__必须给每个实例增加一个新的初始化项self.lines=0,而“状态”对象的 endMessage方法的签名也需要被扩展为 defendMessage(self,tracer):。不过在类TraceQuiet的 endMessage 中会忽略掉这个tacer 参数,因为它从来不用输出任何行;其他两个类的实现则需要增加一行声明 tracer.lines +=1,这是因为它们每条消息只输出一行。
如你所见,这种额外新增的功能要求更紧密的联络,但并不是什么难对付的问题。具体地说,经典状态设计模式的关键特性是,状态切换由状态对象掌握(而在策略设计模式中,切换则来自外部),不过这并不是区分两种设计模式的关键。