Python实现软件设计模式5:原型模式 Prototype Pattern

发布时间 2023-12-20 12:05:57作者: 爱吃砂糖橘的白龙

动机

  • 对象的克隆问题,想要复制出本对象的一个副本,属性方法一模一样
  • 从需求上来说,先快速克隆对象,后续根据需求再进行对象局部属性的修改
  • 区分为深克隆和浅克隆两个版本,默认为浅克隆

角色

  • Prototype 抽象原型类
  • Concrete Prototype 具体原型类
  • Client 客户类

浅克隆与深克隆


模式实例

邮件对象包含多项内容,如发送者、接收者、标题、内容、日期、附件等,系统需要提供一个右键复制功能,对于已有的邮件对象,可以通过复制的方式创建一个新的邮件对象,如果需要改变某部分内容,无需修改原始的邮件对象,只需要修改复制后得到的邮件对象即可。

其中,将附件内容定义为一个引用对象(C/C++语义下的指针类型,或Python语义下的可变对象如列表),代码设计浅克隆和深克隆两个版本。

代码

import copy
from abc import ABC,abstractmethod

class Prototype(ABC):    # 抽象类,声明对象的克隆接口
    @abstractmethod
    def clone(self):    # 返回一个类型为自己的对象
        pass

class Email_S(Prototype):   # shallow copy 
    def __init__(self, sd, rp, tt, date, msg, apd):
        self.sender = sd
        self.recipient = rp
        self.title = tt
        self.date = date
        self.message = msg
        self.appendix = apd     # list

    def clone(self):    # 浅克隆对象
        return copy.copy(self)
    
    def display(self):
        print('-'*50)
        print(f"  Time  : {self.date}    Title:{self.title}")
        print(f" Sender : {self.sender}" + ' '*18 + f"Recipient:{self.recipient}")
        print(f" Message: {self.message}")
        print(f"Appendix: {self.appendix}")
        print('-'*50)
    
    def getAppendix(self):
        return self.appendix

class Email_D(Prototype):   # deep copy
    def __init__(self, sd, rp, tt, date, msg, apd):
        self.sender = sd
        self.recipient = rp
        self.title = tt
        self.date = date
        self.message = msg
        self.appendix = apd     # list

    def clone(self):    # 深克隆对象
        return copy.deepcopy(self)
    
    def display(self):
        print('+'*50)
        print(f"  Time  : {self.date}    Title:{self.title}")
        print(f" Sender : {self.sender}" + ' '*18 + f"Recipient:{self.recipient}")
        print(f" Message: {self.message}")
        print(f"Appendix: {self.appendix}")
        print('-'*50)

if __name__ == '__main__':
    mail1 = Email_S('Alice', 'Bob', 'Marry Me', '2023-12-12 10:00:00', 'I like you very much, please be my wife!', [1,2,3])
    _mail1 = mail1.clone()  # 浅克隆一份

    # mail1.display()
    # _mail1.display()

    mail1.appendix.append(400)  # 修改源对象的引用类型附件对象

    mail1.display() 
    _mail1.display()        # 浅拷贝后可变对象的值也一样

    print("\n")

    mail2 = Email_D('Bob', 'Alice', 'Reply', '2023-12-12 11:00:00', 'Fxxk you, gun ni ma de :) ', [4,5,6])
    _mail2 = mail2.clone()  # 深克隆一份

    # mail2.display()
    # _mail2.display()

    mail2.appendix.append(700)  # 修改源对象的引用类型附件对象

    mail2.display()         # 深拷贝后可变对象的值不一样
    _mail2.display()

总结

  • 优化对象创建过程,通过复制一个已有实例可以提高新实例的创建效率
  • 扩展性好,无需专门的工厂类来创建产品
  • 需要为每一个类配备一个克隆方法(继承Prototype并重写clone方法),当对一个类进行改造时,需要修改、扩展源代码,违背开闭原则
  • 可以保存对象的状态,以便在需要的时候使用,可以辅助实现动作行为撤销操作(这时需要使用深克隆的方式)
  • 要避免使用分层次的工厂类来创建分层次的对象