Python、设计原则和设计模式-对象行为类设计模式(一)
观察者模式
观察者模式的定义
观察者模式(Observer Pattern):定义对象之间的一种一对多依赖关系,使得每当一个对象状态发生改变时,其相关依赖对象皆得到通知并被自动更新。观察者模式的别名包括发布-订阅(Publish/Subscribe)模式、模型-视图(Model/View)模式、源-监听器(Source/Listener)模式或从属者(Dependents)模式。
主要包含抽象对象、具体对象、抽象观察者、具体观察者
观察者模式的示例
class Observer:
# 抽象观察者
def update(self):
pass
class AlarmSensor(Observer):
# 具体观察者
def update(self, action):
print("Alarm Got: %s" % action)
self.runAlarm()
def runAlarm(self):
print("Alarm Ring...")
class WaterSprinker(Observer):
# 具体观察者
def update(self, action):
print("Sprinker Got: %s" % action)
self.runSprinker()
def runSprinker(self):
print("Spray Water...")
class EmergencyDialer(Observer):
# 具体观察者
def update(self, action):
print("Dialer Got: %s" % action)
self.runDialer()
def runDialer(self):
print("Dial 119...")
class Observed:
# 抽象被观察者
observers = []
action = ""
def addObserver(self, observer):
self.observers.append(observer)
def notifyAll(self):
for obs in self.observers:
obs.update(self.action)
class smokeSensor(Observed):
# 具体观察者
def setAction(self, action):
self.action = action
def isFire(self):
return True
if __name__ == "__main__":
alarm = AlarmSensor()
sprinker = WaterSprinker()
dialer = EmergencyDialer()
smoke_sensor = smokeSensor()
smoke_sensor.addObserver(alarm)
smoke_sensor.addObserver(sprinker)
smoke_sensor.addObserver(dialer)
if smoke_sensor.isFire():
smoke_sensor.setAction("On Fire!")
smoke_sensor.notifyAll()
观察者模式的优点
(1) 观察者模式可以实现表示层和数据逻辑层的分离,定义了稳定的消息更新传递机制,并抽象了更新接口,使得可以有各种各样不同的表示层充当具体观察者角色。
(2) 观察者模式在观察目标和观察者之间建立一个抽象的耦合。观察目标只需要维持一个抽象观察者的集合,无须了解其具体观察者。由于观察目标和观察者没有紧密地耦合在一起,因此它们可以属于不同的抽象化层次。
(3) 观察者模式支持广播通信,观察目标会向所有已注册的观察者对象发送通知,简化了一对多系统设计的难度。
(4) 观察者模式满足“开闭原则”的要求,增加新的具体观察者无须修改原有系统代码,在具体观察者与观察目标之间不存在关联关系的情况下,增加新的观察目标也很方便。
观察者模式的缺点
(1) 如果一个观察目标对象有很多直接和间接观察者,将所有的观察者都通知到会花费很多时间。
(2) 如果在观察者和观察目标之间存在循环依赖,观察目标会触发它们之间进行循环调用,可能导致系统崩溃。
(3) 观察者模式没有相应的机制让观察者知道所观察的目标对象是怎么发生变化的,而仅仅只是知道观察目标发生了变化。
观察者模式的适用场景
(1) 一个抽象模型有两个方面,其中一个方面依赖于另一个方面,将这两个方面封装在独立的对象中使它们可以各自独立地改变和复用。
(2) 一个对象的改变将导致一个或多个其他对象也发生改变,而并不知道具体有多少对象将发生改变,也不知道这些对象是谁。
(3) 需要在系统中创建一个触发链,A对象的行为将影响B对象,B对象的行为将影响C对象……,可以使用观察者模式创建一种链式触发机制。
迭代器模式
迭代器模式的定义
迭代器模式(Iterator Pattern):提供一种方法来访问聚合对象,而不用暴露这个对象的内部表示,其别名为游标(Cursor)。迭代器模式是一种对象行为型模式。
主要包括抽象迭代器、具体迭代器、抽象聚合类、具体聚合类
可以考虑使用抽象类来设计抽象迭代器,在抽象类中为每一个方法提供一个空的默认实现。如果需要在具体迭代器中为聚合对象增加全新的遍历操作,则必须修改抽象迭代器和具体迭代器的源代码,这将违反“开闭原则”,因此在设计时要考虑全面,避免之后修改接口。
迭代器模式的示例
在当前,几乎没有人专门去开发一个迭代器,而是直接去使用list、string、set、dict等python可迭代对象,或者直接使用__iter__和next函数来实现迭代器。
class BinaryTree:
# 可迭代对象
def __init__(self, root):
self.key = root
self.leftChild = None
self.rightChild = None
self.height = 0
def insertLeft(self, newNode):
tree = BinaryTree(newNode)
if not self.leftChild:
self.leftChild = tree
else:
# 如果插入位置已有节点,则整体向下挪
# 新的子节点与旧的子节点链接,旧的父节点与新的子节点链接
tree.leftChild = self.leftChild
self.leftChild = tree
self.height += 1
def insertRight(self, newNode):
tree = BinaryTree(newNode)
if not self.rightChild:
self.rightChild = tree
else:
tree.rightChild = self.rightChild
self.rightChild = tree
self.height += 1
def getRightChild(self):
return self.rightChild
def getLeftChild(self):
return self.leftChild
def setRootVal(self, obj):
self.key = obj
def getRootVal(self):
return self.key
def __iter__(self):
return TreeIterator(tree=self)
def __str__(self):
return "<class 'BinaryTree' value: %r >" % self.getRootVal()
class TreeIterator:
# 迭代器
def __init__(self, tree) -> None:
self.tree = tree
self.treeLst = [self.tree]
def __iter__(self):
return self
def __next__(self):
# 采用层级遍历
while len(self.treeLst) > 0:
node = self.treeLst.pop(0)
if node.leftChild:
self.treeLst.append(node.getLeftChild())
if node.rightChild:
self.treeLst.append(node.getRightChild())
return node
raise StopIteration("Tree iter end")
if __name__ == '__main__':
binaryTree = BinaryTree("a")
binaryTree.insertLeft("b")
binaryTree.insertRight("c")
binaryTree.leftChild.insertLeft("d")
binaryTree.leftChild.insertRight("e")
binaryTree.rightChild.insertLeft("f")
for node in binaryTree:
print(node)
迭代器模式的优点
(1) 它支持以不同的方式遍历一个聚合对象,在同一个聚合对象上可以定义多种遍历方式。在迭代器模式中只需要用一个不同的迭代器来替换原有迭代器即可改变遍历算法,我们也可以自己定义迭代器的子类以支持新的遍历方式。
(2) 迭代器简化了聚合类。由于引入了迭代器,在原有的聚合对象中不需要再自行提供数据遍历等方法,这样可以简化聚合类的设计。
(3) 在迭代器模式中,由于引入了抽象层,增加新的聚合类和迭代器类都很方便,无须修改原有代码,满足“开闭原则”的要求。
迭代器模式的缺点
(1) 由于迭代器模式将存储数据和遍历数据的职责分离,增加新的聚合类需要对应增加新的迭代器类,类的个数成对增加,这在一定程度上增加了系统的复杂性。
(2) 抽象迭代器的设计难度较大,需要充分考虑到系统将来的扩展,例如JDK内置迭代器Iterator就无法实现逆向遍历,如果需要实现逆向遍历,只能通过其子类ListIterator等来实现,而ListIterator迭代器无法用于操作Set类型的聚合对象。在自定义迭代器时,创建一个考虑全面的抽象迭代器并不是件很容易的事情。
迭代器模式的适用场景
(1) 访问一个聚合对象的内容而无须暴露它的内部表示。将聚合对象的访问与内部数据的存储分离,使得访问聚合对象时无须了解其内部实现细节。
(2) 需要为一个聚合对象提供多种遍历方式。
(3) 为遍历不同的聚合结构提供一个统一的接口,在该接口的实现类中为不同的聚合结构提供不同的遍历方式,而客户端可以一致性地操作该接口。
责任链模式
责任链模式的定义
职责链模式(Chain of Responsibility Pattern):避免请求发送者与接收者耦合在一起,让多个对象都有可能接收请求,将这些对象连接成一条链,并且沿着这条链传递请求,直到有对象处理它为止。
一个纯的职责链模式要求一个具体处理者对象只能在两个行为中选择一个:要么承担全部责任,要么将责任推给下家,不允许出现某一个具体处理者对象在承担了一部分或全部责任后又将责任向下传递的情况。而且在纯的职责链模式中,要求一个请求必须被某一个处理者对象所接收,不能出现某个请求未被任何一个处理者对象处理的情况。
在一个不纯的职责链模式中允许某个请求被一个具体处理者部分处理后再向下传递,或者一个具体处理者处理完某请求后其后继处理者可以继续处理该请求,而且一个请求可以最终不被任何处理者对象所接收。
责任链模式的示例
from abc import ABCMeta, abstractmethod
class Handler(metaclass=ABCMeta):
# 抽象的处理者
@abstractmethod
def handle_leave(self, day):
pass
class GeneralManager(Handler):
# 具体的处理者
def handle_leave(self, day):
if day <= 30:
print('总经理准假%d' % day)
else:
print('可以辞职了!')
class DepartmentManager(Handler):
# 具体的处理者
def __init__(self):
self.next = GeneralManager()
def handle_leave(self, day):
if day <= 7:
print('部门经理准假%d' % day)
else:
print('部门经理职权不足')
self.next.handle_leave(day)
class ProjectDirector(Handler):
# 具体的处理者
def __init__(self):
self.next = DepartmentManager()
def handle_leave(self, day):
if day <= 3:
print('项目主管准假%d' % day)
else:
print('项目主管职权不足')
self.next.handle_leave(day)
day = 20
p = ProjectDirector()
p.handle_leave(day)
"""
项目主管职权不足
部门经理职权不足
总经理准假20
"""
责任链模式的优点
(1) 职责链模式使得一个对象无须知道是其他哪一个对象处理其请求,对象仅需知道该请求会被处理即可,接收者和发送者都没有对方的明确信息,且链中的对象不需要知道链的结构,由客户端负责链的创建,降低了系统的耦合度。
(2) 请求处理对象仅需维持一个指向其后继者的引用,而不需要维持它对所有的候选处理者的引用,可简化对象的相互连接。
(3) 在给对象分派职责时,职责链可以给我们更多的灵活性,可以通过在运行时对该链进行动态的增加或修改来增加或改变处理一个请求的职责。
(4) 在系统中增加一个新的具体请求处理者时无须修改原有系统的代码,只需要在客户端重新建链即可,从这一点来看是符合“开闭原则”的。
责任链模式的缺点
(1) 由于一个请求没有明确的接收者,那么就不能保证它一定会被处理,该请求可能一直到链的末端都得不到处理;一个请求也可能因职责链没有被正确配置而得不到处理。
(2) 对于比较长的职责链,请求的处理可能涉及到多个处理对象,系统性能将受到一定影响,而且在进行代码调试时不太方便。
(3) 如果建链不当,可能会造成循环调用,将导致系统陷入死循环。
责任链模式的适用场景
(1) 有多个对象可以处理同一个请求,具体哪个对象处理该请求待运行时刻再确定,客户端只需将请求提交到链上,而无须关心请求的处理对象是谁以及它是如何处理的。
(2) 在不明确指定接收者的情况下,向多个对象中的一个提交一个请求。
(3) 可动态指定一组对象处理请求,客户端可以动态创建职责链来处理请求,还可以改变链中处理者之间的先后次序。
命令模式
命令模式的定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。
命令模式的本质是对请求进行封装,一个请求对应于一个命令,将发出命令的责任和执行命令的责任分割开。每一个命令都是一个操作:请求的一方发出请求要求执行一个操作;接收的一方收到请求,并执行相应的操作。命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求如何被接收、操作是否被执行、何时被执行,以及是怎么被执行的。
命令模式的关键在于引入了抽象命令类,请求发送者针对抽象命令类编程,只有实现了抽象命令类的具体命令才与请求接收者相关联。在最简单的抽象命令类中只包含了一个抽象的execute()方法,每个具体命令类将一个Receiver类型的对象作为一个实例变量进行存储,从而具体指定一个请求的接收者,不同的具体命令类提供了execute()方法的不同实现,并调用不同接收者的请求处理方法。
宏命令(Macro Command)又称为组合命令,它是组合模式和命令模式联用的产物。宏命令是一个具体命令类,它拥有一个集合属性,在该集合中包含了对其他命令对象的引用。通常宏命令不直接与请求接收者交互,而是通过它的成员来调用接收者的方法。当调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法,一个宏命令的成员可以是简单命令,还可以继续是宏命令。
命令模式的示例
class backSys():
def cook(self, dish):
pass
class mainFoodSys(backSys):
def cook(self, dish):
print("MAINFOOD:Cook %s" % dish)
class coolDishSys(backSys):
def cook(self, dish):
print("COOLDISH:Cook %s" % dish)
class hotDishSys(backSys):
def cook(self, dish):
print("HOTDISH:Cook %s" % dish)
class waiterSys():
menu_map = dict()
commandList = []
def setOrder(self, command):
print("WAITER:Add dish")
self.commandList.append(command)
def cancelOrder(self, command):
print("WAITER:Cancel order...")
self.commandList.remove(command)
def notify(self):
print("WAITER:Nofify...")
for command in self.commandList:
command.execute()
class Command():
# 抽象命令类
receiver = None
def __init__(self, receiver):
self.receiver = receiver
def execute(self):
pass
class foodCommand(Command):
# 具体命令类
dish = ""
def __init__(self, receiver, dish):
self.receiver = receiver
self.dish = dish
def execute(self):
self.receiver.cook(self.dish)
class mainFoodCommand(foodCommand):
# 具体命令类
pass
class coolDishCommand(foodCommand):
# 具体命令类
pass
class hotDishCommand(foodCommand):
# 具体命令类
pass
class menuAll:
menu_map = dict()
def loadMenu(self): # 加载菜单,这里直接写死
self.menu_map["hot"] = ["Yu-Shiang Shredded Pork",
"Sauteed Tofu, Home Style", "Sauteed Snow Peas"]
self.menu_map["cool"] = ["Cucumber", "Preserved egg"]
self.menu_map["main"] = ["Rice", "Pie"]
def isHot(self, dish):
if dish in self.menu_map["hot"]:
return True
return False
def isCool(self, dish):
if dish in self.menu_map["cool"]:
return True
return False
def isMain(self, dish):
if dish in self.menu_map["main"]:
return True
return False
if __name__ == "__main__":
dish_list = ["Yu-Shiang Shredded Pork",
"Sauteed Tofu, Home Style", "Cucumber", "Rice"] # 顾客点的菜
waiter_sys = waiterSys()
main_food_sys = mainFoodSys()
cool_dish_sys = coolDishSys()
hot_dish_sys = hotDishSys()
menu = menuAll()
menu.loadMenu()
for dish in dish_list:
if menu.isCool(dish):
cmd = coolDishCommand(cool_dish_sys, dish)
elif menu.isHot(dish):
cmd = hotDishCommand(hot_dish_sys, dish)
elif menu.isMain(dish):
cmd = mainFoodCommand(main_food_sys, dish)
else:
continue
waiter_sys.setOrder(cmd)
waiter_sys.notify()
命令模式的优点
(1) 降低系统的耦合度。由于请求者与接收者之间不存在直接引用,因此请求者与接收者之间实现完全解耦,相同的请求者可以对应不同的接收者,同样,相同的接收者也可以供不同的请求者使用,两者之间具有良好的独立性。
(2) 新的命令可以很容易地加入到系统中。由于增加新的具体命令类不会影响到其他类,因此增加新的具体命令类很容易,无须修改原有系统源代码,甚至客户类代码,满足“开闭原则”的要求。
(3) 可以比较容易地设计一个命令队列或宏命令(组合命令)。
(4) 为请求的撤销(Undo)和恢复(Redo)操作提供了一种设计和实现方案。
命令模式的缺点
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个对请求接收者的调用操作都需要设计一个具体命令类,因此在某些系统中可能需要提供大量的具体命令类,这将影响命令模式的使用。
命令模式的适用场景
(1) 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。请求调用者无须知道接收者的存在,也无须知道接收者是谁,接收者也无须关心何时被调用。
(2) 系统需要在不同的时间指定请求、将请求排队和执行请求。一个命令对象和请求的初始调用者可以有不同的生命期,换言之,最初的请求发出者可能已经不在了,而命令对象本身仍然是活动的,可以通过该命令对象去调用请求接收者,而无须关心请求调用者的存在性,可以通过请求日志文件等机制来具体实现。
(3) 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
(4) 系统需要将一组操作组合在一起形成宏命令。
备忘录模式
备忘录模式的定义
备忘录模式(Memento Pattern):在不破坏封装的前提下,捕获一个对象的内部状态,并在该对象之外保存这个状态,这样可以在以后将对象恢复到原先保存的状态。它是一种对象行为型模式,其别名为Token。
● Originator(原发器):它是一个普通类,可以创建一个备忘录,并存储它的当前内部状态,也可以使用备忘录来恢复其内部状态,一般将需要保存内部状态的类设计为原发器。
●Memento(备忘录):存储原发器的内部状态,根据原发器来决定保存哪些内部状态。备忘录的设计一般可以参考原发器的设计,根据实际需要确定备忘录类中的属性。需要注意的是,除了原发器本身与负责人类之外,备忘录对象不能直接供其他类使用,原发器的设计在不同的编程语言中实现机制会有所不同。
●Caretaker(负责人):负责人又称为管理者,它负责保存备忘录,但是不能对备忘录的内容进行操作或检查。在负责人类中可以存储一个或多个备忘录对象,它只负责存储对象,而不能修改对象,也无须知道对象的实现细节。
备忘录模式的示例
import random
class GameCharacter():
# 原发器类
vitality = 0
attack = 0
defense = 0
def displayState(self):
print('Current Values:')
print('Life:%d' % self.vitality)
print('Attack:%d' % self.attack)
print('Defence:%d' % self.defense)
def initState(self, vitality, attack, defense):
self.vitality = vitality
self.attack = attack
self.defense = defense
def saveState(self):
return Memento(self.vitality, self.attack, self.defense)
def recoverState(self, memento):
self.vitality = memento.vitality
self.attack = memento.attack
self.defense = memento.defense
class FightCaretaker(GameCharacter):
# 负责人类
def fight(self):
self.vitality -= random.randint(1, 10)
class Memento:
# 备忘录类
vitality = 0
attack = 0
defense = 0
def __init__(self, vitality, attack, defense):
self.vitality = vitality
self.attack = attack
self.defense = defense
if __name__ == "__main__":
game_chrctr = FightCaretaker()
game_chrctr.initState(100, 79, 60)
game_chrctr.displayState()
memento = game_chrctr.saveState()
game_chrctr.fight()
game_chrctr.displayState()
game_chrctr.recoverState(memento)
game_chrctr.displayState()
备忘录模式的优点
(1)它提供了一种状态恢复的实现机制,使得用户可以方便地回到一个特定的历史步骤,当新的状态无效或者存在问题时,可以使用暂时存储起来的备忘录将状态复原。
(2)备忘录实现了对信息的封装,一个备忘录对象是一种原发器对象状态的表示,不会被其他代码所改动。备忘录保存了原发器的状态,采用列表、堆栈等集合来存储备忘录对象可以实现多次撤销操作。
备忘录模式的缺点
资源消耗过大,如果需要保存的原发器类的成员变量太多,就不可避免需要占用大量的存储空间,每保存一次对象的状态都需要消耗一定的系统资源。
备忘录模式的适用场景
(1)保存一个对象在某一个时刻的全部状态或部分状态,这样以后需要时它能够恢复到先前的状态,实现撤销操作。
(2)防止外界对象破坏一个对象历史状态的封装性,避免将对象历史状态的实现细节暴露给外界对象。
状态模式
状态模式的定义
状态模式(State Pattern):允许一个对象在其内部状态改变时改变它的行为,对象看起来似乎修改了它的类。其别名为状态对象(Objects for States)。
包括环境类、抽象状态类、具体状态类
状态模式的示例
class LiftState:
# 抽象状态类
def open(self):
pass
def close(self):
pass
def run(self):
pass
def stop(self):
pass
class OpenState(LiftState):
# 具体状态类
def open(self):
print("OPEN:The door is opened...")
return self
def close(self):
print("OPEN:The door start to close...")
print("OPEN:The door is closed")
return StopState()
def run(self):
print("OPEN:Run Forbidden.")
return self
def stop(self):
print("OPEN:Stop Forbidden.")
return self
class RunState(LiftState):
# 具体状态类
def open(self):
print("RUN:Open Forbidden.")
return self
def close(self):
print("RUN:Close Forbidden.")
return self
def run(self):
print("RUN:The lift is running...")
return self
def stop(self):
print("RUN:The lift start to stop...")
print("RUN:The lift stopped...")
return StopState()
class StopState(LiftState):
# 具体状态类
def open(self):
print("STOP:The door is opening...")
print("STOP:The door is opened...")
return OpenState()
def close(self):
print("STOP:Close Forbidden")
return self
def run(self):
print("STOP:The lift start to run...")
return RunState()
def stop(self):
print("STOP:The lift is stopped.")
return self
class Context:
# 环境类
lift_state = ""
def getState(self):
return self.lift_state
def setState(self, lift_state):
self.lift_state = lift_state
def open(self):
self.setState(self.lift_state.open())
def close(self):
self.setState(self.lift_state.close())
def run(self):
self.setState(self.lift_state.run())
def stop(self):
self.setState(self.lift_state.stop())
if __name__ == "__main__":
ctx = Context()
ctx.setState(StopState())
ctx.open()
ctx.run()
ctx.close()
ctx.run()
ctx.stop()
状态模式的优点
(1) 封装了状态的转换规则,在状态模式中可以将状态的转换代码封装在环境类或者具体状态类中,可以对状态转换代码进行集中管理,而不是分散在一个个业务方法中。
(2) 将所有与某个状态有关的行为放到一个类中,只需要注入一个不同的状态对象即可使环境对象拥有不同的行为。
(3) 允许状态转换逻辑与状态对象合成一体,而不是提供一个巨大的条件语句块,状态模式可以让我们避免使用庞大的条件语句来将业务方法和状态转换代码交织在一起。
(4) 可以让多个环境对象共享一个状态对象,从而减少系统中对象的个数。
状态模式的缺点
(1) 状态模式的使用必然会增加系统中类和对象的个数,导致系统运行开销增大。
(2) 状态模式的结构与实现都较为复杂,如果使用不当将导致程序结构和代码的混乱,增加系统设计的难度。
(3) 状态模式对“开闭原则”的支持并不太好,增加新的状态类需要修改那些负责状态转换的源代码,否则无法转换到新增状态;而且修改某个状态类的行为也需修改对应类的源代码。
状态模式的适用场景
(1) 对象的行为依赖于它的状态(如某些属性值),状态的改变将导致行为的变化。
(2) 在代码中包含大量与对象状态有关的条件语句,这些条件语句的出现,会导致代码的可维护性和灵活性变差,不能方便地增加和删除状态,并且导致客户类与类库之间的耦合增强。