面向对象设计模式(Python版)-- 行为模式

面向对象设计模式(Python版)-- 行为模式

12.Command 命令模式

命令模式是一种行为设计模式,通过将执行某一动作或触发某一事件所需的所有信息封装在一个对象中,使其可以在稍后时间执行。这些信息包括方法名、拥有该方法的对象和方法的参数等。 下面以遥控器控制电灯为例,按照一般的编程方式,我们可以将遥控器和电灯分别构建一个类,将控制命令封装在遥控器类中,电灯的开关功能封装在电灯类中。但是这样的设计模式,难以实现接收者(如电灯)和调用者(如遥控器)之间的解耦,因为调用者直接控制接收者。命令模式在这两者之间添加了一个命令类,类似于一个协调者,使得接收者和调用者之间的信息流通更加灵活(如撤销和恢复功能,记录操作等等)。示例代码如下。

# 接收者
class Bulb:
    def turnOn(self):
        print("Bulb has been lit")

    def turnOff(self):
        print("Darkness!")

# 命令
class Command:
    def execute(self):
        pass
    def undo(self):
        pass
    def redo(self):
        pass

class TurnOn(Command):
    def __init__(self, bulb):
        self.bulb = bulb
    def execute(self):
        self.bulb.turnOn()
    def undo(self):
        self.bulb.turnOff()
    def redo(self):
        self.bulb.turnOn()

class TurnOff(Command):
    def __init__(self, bulb):
        self.bulb = bulb
    def execute(self):
        self.bulb.turnOff()
    def undo(self):
        self.bulb.turnOn()
    def redo(self):
        self.bulb.turnOff()

# 调用者类
class RemoteControl:
    def submit(self, command):
        command.execute()

if __name__ == "__main__":
    bulb = Bulb()
    turnon = TurnOn(bulb)
    turnoff = TurnOff(bulb)
    remote = RemoteControl()
    remote.submit(turnon)
    remote.submit(turnoff)

从上述可以看出,调用者和接收者之间没有什么直接关系,两者通过命令对象连接在一起。这样做的好处在于,命令的添加和组合更加方便。

命令模式适合用于实现命令的调度、记录、撤销和重做等功能。当你需要将操作封装为对象,通过操作的排列组合来调控具体行为时,命令模式就显得非常有用。命令设计模式通过将请求封装成对象,可以让系统支持复杂的命令、请求和操作的管理。它提供了一种扩展系统功能的灵活方式,同时保持调用者和接收者之间的解耦。


13.Observer 观察者模式

观察者模式也叫发布-订阅模式,它定义了一种依赖关系,当一个对象状态改变时,所有依赖于它的对象都会得到通知并自动更新。观察者模式中一个对象称为主题(subject),它维护一组依赖它的对象,称为观察者(observers)。当主题的状态发送变化时,它会主动通知所有观察者。下面模拟一个招聘网站发布岗位信息,当求职者订阅了职位发布者,那么当有新的岗位出现时,职位发布者就会主动通知到相应的求职者。

# 职位公告
class JobPost:
    def __init__(self, title):
        self.title = title
    def getTitle(self):
        return self.title

# 观察者
class JobSeeker:
    def __init__(self, name):
        self.name = name
    # 当有新的职位公告时,调用此方法
    def onJobPosted(self, job):
        print(f'{self.name} got a new job post: {job.getTitle()}')

# 职位发布者(主题)
class EmploymentAgency:
    def __init__(self):
        # 作为演示,没有构建职业池存储招聘岗位
        self.job_seekers = []
    # 求职者订阅后,添加进求职者池
    def addJobSeeker(self, job_seeker):
        self.job_seekers.append(job_seeker)
    def detachJobSeeker(self, job_seeker):
        self.job_seekers.remove(job_seeker)
    def postJob(self, job):
        for job_seeker in self.job_seekers:
            job_seeker.onJobPosted(job)
    def addJob(self, job):
        self.postJob(job)

if __name__ == '__main__':
    zhangsan = JobSeeker('zhangsan')
    lisi = JobSeeker('lisi')
    agency = EmploymentAgency()
    agency.addJobSeeker(zhangsan)
    agency.addJobSeeker(lisi)
    agency.addJob(JobPost('Python Developer'))

上述代码分别实现了订阅者(观察者)和发布者(主题),消息通知的方法是定义在订阅者类中的,这样不同的订阅者可以选择自己想要的通知模式;发布者内部维护了一个订阅者列表,当发布新消息时,向列表中的每一个订阅者发布消息。

当你希望一个对象状态改变时通知其他多个对象,而不希望这些对象之间存在紧耦合关系时,可以使用观察者模式。这种模式在需要处理事件通知的系统中非常有用,例如求职网站、事件驱动系统等。


14.Template模板方法模式

模板方法是一种行为设计模式,它在基类中定义了一个算法的骨架,并允许子类在不改变算法结构的前提下重新定义算法的某些步骤。以咖啡和茶的冲泡过程为例,它们具有相同的流程,但是在细节部分有所不同,因此采用模板方法可以抽象其过程。示例代码如下。

# 构建冲泡饮料的模板方法
class Beverage:
    def brew(self):
        pass
    def addCondiments(self):
        pass
    def pourInCup(self):
        print("Pouring in cup")
    def boilWater(self):
        print("Boiling water")
    def prepareRecipe(self):
        self.boilWater()
        self.brew()
        self.pourInCup()
        self.addCondiments()

# 实现冲泡咖啡
class Coffee(Beverage):
    def brew(self):
        print("Brewing coffee")
    def addCondiments(self):
        print("Adding sugar and milk")
# 实现泡茶
class Tea(Beverage):
    def brew(self):
        print("Brewing tea")
    def addCondiments(self):
        print("Adding lemon")

if __name__ == '__main__':
    coffee = Coffee()
    coffee.prepareRecipe()
    print("-----")
    tea = Tea()
    tea.prepareRecipe()

从上述代码可以看出模板方法确定了算法的整体框架,因此prepareRecipe()方法不应该被子类改写。模板方法适用于当你有一个通用的流程需要多个类共享,但某些步骤需要根据具体情况进行自定义时,可以考虑使用模板方法设计模式。


15.Strategy策略模式

策略模式将算法的定义和使用分离,使得算法可以在不修改客户端代码的情况下独立变化。

模板方法和策略模式都是用于定义算法的设计模式,但它们的侧重点不同。模板方法强调的是在一个通用框架内定义算法的结构,然后让子类根据需要重写部分步骤。它关注的是一个固定流程,而不是选择不同的算法。而策略模式则更关注算法的可替换性。它允许你在运行时选择不同的算法实现,但不会影响算法的结构。策略模式将算法的每个实现封装在独立的类中,并通过接口使它们可以互换使用,而模板方法则是通过继承来实现算法结构的部分定制。

下面以超市收银台结账为例,用户可以选择不同的支付方式,每种支付方式都有各自的流程。示例代码如下。

import types

class PaymentStrategy:
    def __init__(self, payFun=None):
        self.name = "sqy"
        if payFun is not None:
            self.payMoney = types.MethodType(payFun, self)
    
    def payMoney(self, amount):
        print(f"{self.name}默认采用支付宝支付:{amount}")

def creditCard(self,amount):
    print(f"{self.name}采用信用卡支付:{amount}")

def weiXin(self,amount):
    print(f"{self.name}采用微信支付:{amount}")

if __name__ == '__main__':
    pay1 = PaymentStrategy()
    pay2 = PaymentStrategy(creditCard)
    pay3 = PaymentStrategy(weiXin)
    pay1.payMoney(100)
    pay2.payMoney(200)
    pay3.payMoney(300)

上述代码采用了基于python更加灵活的实现方式。利用types模块将一般函数绑定到对象中,称为成员函数。一般实现可以为每个支付方式创建一个类。需要注意的是,这里为了演示方便没有创建客户端类,在客户端支付时,将相应支付方式的实例化对象传入,完成支付。


16. Chain of Responsibility 责任链

责任链模式是指构建一个对象链。请求从链的一端进入,然后沿着链一个一个传递,直到找到合适的处理者,是一种行为设计模式。我们以高校请假手续为例,班主任可以批最多3天的假,学工处可以批最多两周的假期,学校可以批一年的假期。当学生需要请假时,他只需要向班主任请假,如果请假时间过长,自动移交给下一级审批。示例代码如下。

class Handler:
    def __init__(self):
        self.successor = None
    def setSuccessor(self, successor):
        self.successor = successor

class ConcreteHandler(Handler):
    def __init__(self,role,day=0):
        self.day = day
        self.role = role
    def handle(self, request):
        if 0< request <= self.day:
            print(f"{self.role}审批成功,批假{request}天")
        else:
            self.successor.handle(request)

if __name__ == "__main__":
    requests = [3,7,30]
    class_teacher = ConcreteHandler("班主任",3)
    counsellor = ConcreteHandler("学工处",14)
    vice_principal = ConcreteHandler("校办",365)
    class_teacher.setSuccessor(counsellor)
    counsellor.setSuccessor(vice_principal)
    for request in requests:
        class_teacher.handle(request)

从示例代码可以看出,责任链的核心在于成员变量sucessor,它保存了下一级处理事物的对象,同时该对象具有相同的处理接口。

责任链的优点在于将请求者与处理者分离,请求者并不知道请求最后是被哪个处理者所处理。且不同处理者之间也是解耦的,因此,容易扩展。在实际开发中,职责链模式可以帮助我们创建更灵活的系统结构,提高代码的可维护性和扩展性。

责任链的缺点在于单一链使得每个请求都要遍历整条链,这样效率较为低下。可以采用树结构来进行提升。


17.Mediator中介者

中介者模式引入一个第三方对象(称为中介者)来控制两个或多个对象(称为同事)之间的交互,从而减少类之间的耦合,因为它们不再需要了解彼此的实现细节,是一种行为模式

与代理模型相比,中介者模式侧重于简化对象之间的通信,通过一个中介者对象来管理和协调多个对象之间的交互。而代理模式则是通过一个代理对象来控制对另一个对象的访问,通常用于添加额外的功能,如权限控制,延迟加载等。

我们以微信群为例,在微信群中,一个用户发送消息,其他用户都能接收到该消息。该微信群终端扮演者中介者角色。示例代码如下。

class ChatMediator:
    def __init__(self,group_name):
        self._users = []
        self.group_name = group_name
    def addUser(self, user):
        self._users.append(user)
    def sendMessage(self, message, user):
        for u in self._users:
            if u != user:
                u.receiveMessage(self.group_name+f"({u.name})"+">>>From "+user.name+": "+message)
            else:
                u.receiveMessage(self.group_name+f"({u.name})"+">>> "+message)
class User:
    def __init__(self, name, mediator):
        self.name = name
        self.mediator = mediator
    def sendMessage(self, message):
        self.mediator.sendMessage(message, self)

    def receiveMessage(self, message):
        print(message)

if __name__ == '__main__':
    mediator = ChatMediator("工作群1")
    mingming = User("小明", mediator)
    pangpang = User("胖虎", mediator)
    wangcai = User("旺财", mediator)
    mediator.addUser(mingming)
    mediator.addUser(pangpang)
    mediator.addUser(wangcai)
    mingming.sendMessage("开工!")
    pangpang.sendMessage("收到!")
    

当你希望减少对象之间的复杂依赖关系,促进对象解耦时,可以使用中介者模式。比如机场调度系统(多个跑道、飞机、指挥塔之间的调度),路由系统,MVC框架其中C(controller)就是M(Model)和V(View)的中介者。


18.State状态模式

状态模式是一种行为设计模式。它以面向对象的方式实现状态机。使用状态模式时,每个状态都会被实现为状态接口的一个派生类,状态的转换通过调用接口中定义的方法来处理。状态模式可以看作是策略模式的一种变体,其中当前的策略会根据状态转换而改变。

我们以咖啡机为例,假设咖啡机一共三种状态"空闲状态"、"选择咖啡状态"和"待取咖啡状态"。每当用户完成一项操作之后,咖啡机的状态就会发生改变。示例代码如下。

from abc import ABC, abstractmethod
# 咖啡机的状态基类
class CoffeeMachineState(ABC):
    @abstractmethod
    def insertCoin():
        pass
    @abstractmethod
    def selectCoffee():
        pass
    @abstractmethod
    def dispenseCoffee():
        pass
# 实现咖啡机的三个状态类
class IdleState(CoffeeMachineState):
    def __init__(self, machine):
        self.machine = machine
    def insertCoin(self):
        print("投入硬币成功,请选择咖啡。")
        self.machine.setState(self.machine.selecting_state)
    def selectCoffee(self):
        print("请先投币。")
    def dispenseCoffee(self):
        print("请先投币并选择咖啡。")
    def __repr__(self):
        return "IdleState"

class SelectingState(CoffeeMachineState):
    def __init__(self, machine):
        self.machine = machine
    def insertCoin(self):
        print("硬币已投入,不要重复投币,请选择咖啡。")
    def selectCoffee(self):
        print("咖啡正在制作中.....")
        self.machine.setState(self.machine.dispensing_state)
    def dispenseCoffee(self):
        print("请先选择咖啡。")
    def __repr__(self):
        return "SelectingState"

class DispensingState(CoffeeMachineState):
    def __init__(self, machine):
        self.machine = machine
    def insertCoin(self):
        print("咖啡已制作完成,不要投币。")
    def selectCoffee(self):
        print("咖啡已制作完成,不要选择咖啡。")
    def dispenseCoffee(self):
        print("请取走您的咖啡。")
        self.machine.setState(self.machine.idle_state)
    def __repr__(self):
        return "DispensingState"

# 咖啡机类
class CoffeeMachine:
    def __init__(self):
        self.idle_state = IdleState(self)
        self.selecting_state = SelectingState(self)
        self.dispensing_state = DispensingState(self)
        self.current_state = self.idle_state
    def setState(self, state):
        self.current_state = state
    def insertCoin(self):
        self.current_state.insertCoin()
    def selectCoffee(self):
        self.current_state.selectCoffee()
    def dispenseCoffee(self):
        self.current_state.dispenseCoffee()
    
if __name__ == '__main__':
    machine = CoffeeMachine()
    operations = []
    operations.append(machine.insertCoin)
    operations.append(machine.selectCoffee)
    operations.append(machine.dispenseCoffee)
    operations.append(
    machine.dispenseCoffee)
    for operation in operations:
        print(f"当前咖啡机状态: {machine.current_state}")
        operation()

从上面的示例代码可以看出不同状态之间实现了解耦,可以灵活的组合各个状态。因此,当一个对象的行为依赖于它的状态,并且需要在运行时根据状态切换行为时,可以考虑使用状态模式。


19.Memento备忘录模式

备忘录模式是一种行为设计模式,用于在不破坏封装的情况下,捕获对象的内部状态,并在之后恢复该状态。

import copy
# 备忘录类用于保存对象的状态
class Memento:
    def __init__(self, state):
        self._state = state

    def get_state(self):
        return self._state

# 文本编辑类
class TextEditor:
    def __init__(self):
        self.content = []

    def type(self, s):
        self.content.append(s)
    
    # 这里要注意,保存的是当前对象的一个深拷贝,而不是引用
    def save(self):
        return Memento(copy.deepcopy(self.content))

    def restore(self, memento):
        self.content = memento.get_state()

# 管理者类用于管理备忘录
class CareTaker:
    def __init__(self):
        self.mementos = []

    def save(self, memento):
        self.mementos.append(memento)

    def undo(self):
        if len(self.mementos) > 0:
            memento = self.mementos.pop()
            return memento
        else:
            return None
    
    def getMemento(self, index):
        return self.mementos[index]

if __name__ == '__main__':
    editor = TextEditor()
    taker = CareTaker()
    editor.type('hello')
    taker.save(editor.save())
    editor.type('world')
    taker.save(editor.save())
    memento = taker.getMemento(0)
    print(f"回到第一次键入的状态:{memento.get_state()}")
    memento = taker.getMemento(1)
    print(f"回到第二次键入的状态:{memento.get_state()}")

从上述代码中可以看出,备忘录模式在保存和恢复对象状态时,不会直接访问或修改对象的内部属性和方法,而是构建或加载Memento对象,从而保持了对象的封装性。

在这个过程中,备忘录对象充当了一个快照的角色,它记录了发起者某一时刻的内部状态。而管理者(Caretaker)则负责保存和提供备忘录对象,但它并不关心备忘录对象的具体内容,只是将其作为一个黑盒进行存储和管理。


20.Iterator迭代器模式

当需要以一种一致的方法遍历集合中的元素,而不想暴露其内部表示时,可以使用迭代器模式。迭代器模式时一种行为设计模式,它用于遍历容器中的元素。迭代器模式将算法与容器解耦。

在python中迭代器的应用非常广泛,已经融入到常用的对象中,如可迭代对象listdictstring等等。下面的代码显式的使用iternext方法来实现迭代器。

class ComputeNumber:
    def __init__(self,n):
        self.n = n
        self.index = 0
    
    def __iter__(self):
        return self
    # 一定要判断遍历条件,当不满足时返回StopIteration
    def __next__(self):
        if self.index < self.n:
            value = self.index**2
            self.index += 1
            return value
        else:
            raise StopIteration()

if __name__ == '__main__':
    print("逐个遍历:")
    x = ComputeNumber(10)
    for i in range(4):
        print(next(x),end=',')
    print("\nfor循环遍历:")
    for s in x:
        print(s,end=',')

从上述代码中可以看出,使用迭代器的过程只需要不断使用next()方法去获得容器中的下一个元素,而不用操心容器中的元素是如何生成的。在深度学习中,数据的生成采用这样的模式,如pytorch中IterableDataset实现数据的预处理和Tensor化。

21.Interpreter解释器模式

解释器模式(Interpreter Pattern)是一种行为型设计模式,它定义了一种语言的语法规则,并通过解释这些规则来处理相应的语言表达式。

#!/usr/bin/python
#coding:utf8
'''
Interpreter
'''
class Expression:
    """抽象表达式类"""
    def interpret(self, context):
        pass

class Constant(Expression):
    """终结符表达式类,表示常量"""
    def __init__(self, value):
        self.value = value

    def interpret(self, context):
        return self.value

class Variable(Expression):
    """终结符表达式类,表示变量"""
    def __init__(self, name):
        self.name = name

    def interpret(self, context):
        return context[self.name]

class Add(Expression):
    """非终结符表达式类,表示加法"""
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def interpret(self, context):
        return self.left.interpret(context) + self.right.interpret(context)

class Multiply(Expression):
    """非终结符表达式类,表示乘法"""
    def __init__(self, left, right):
        self.left = left
        self.right = right

    def interpret(self, context):
        return self.left.interpret(context) * self.right.interpret(context)

# 测试解释器
if __name__ == "__main__":
    # 创建一个上下文(变量表)
    context = {'a': 5, 'b': 3}

    # 构造表达式:a + b * a + 10
    expr = Add(Add(Variable('a'), Multiply(Variable('b'), Variable('a'))),Constant(10))

    # 解释并执行表达式
    result = expr.interpret(context)
    print(f"Result of 'a + b * a + 10' is: {result}")  # 输出结果应为 30

上述代码实现了复合运算公式的解释过程,构建了一个简单抽象语法树,叶节点就是最基本的元素如Constant常量对象和Variable变量对象,子树是各类运算如Add加法。解释器模式更多的用于数据的格式转换和日志处理中。


22.Visitor访问者模式

在面向对象编程中,访问者模式通过将算法与它操作的对象结构分离,允许在不修改这些对象结构的情况下为其添加新操作。这也符合开放封闭原则。这种模式适用于那些对象结构稳定,但需要经常添加新操作的场景。

我们以小说系统为例,当儿童访问童话书时,它会表示欢迎;当打开武侠书时,它会推荐童话书。随着面向的用户类型不断增加,同样的数据在不同场景下的响应不同,这时访问者模式更具优势。

class Novel:
    def __init__(self, name):
        self.name = name
    def accept(self, visitor):
        pass
class FairyTales(Novel):
    def accept(self, visitor):
        visitor.visitFairy(self)

class Chivalry(Novel):
    def accept(self, visitor):
        visitor.visitChivalry(self)

class Children:
    def __init__(self,name):
        self.name = name
    def visitFairy(self, novel):
        print(f"欢迎{self.name}来到《{novel.name}》!")
    def visitChivalry(self, novel):
        print(f"欢迎{self.name},《{novel.name}》可能不适合你,推荐访问童话故事!")

if __name__ == '__main__':
    novel1 = FairyTales('白雪公主')
    novel2 = Chivalry('倚天屠龙记')
    child = Children('小明')
    novel1.accept(child)
    novel2.accept(child)

从上述代码可以看出,小说系统中书籍的类型是确定的,这就意味着小说系统的对象结构稳定。而用户的场景是会不断发生变化的。这种情况下,只需要在不同场景中,分别实现对不同书籍类型的响应,添加新的操作。

访问者模式的被访问对象应该是固定的,如果被访问对象发生改变,会导致访问者需要进行修改。

posted @ 2025-03-12 12:47  不秃头的程序员不秃头  阅读(75)  评论(0)    收藏  举报