动机
- 对象的克隆问题,想要复制出本对象的一个副本,属性方法一模一样
- 从需求上来说,先快速克隆对象,后续根据需求再进行对象局部属性的修改
- 区分为深克隆和浅克隆两个版本,默认为浅克隆
角色
- 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方法),当对一个类进行改造时,需要修改、扩展源代码,违背开闭原则
- 可以保存对象的状态,以便在需要的时候使用,可以辅助实现动作行为撤销操作(这时需要使用深克隆的方式)
- 要避免使用分层次的工厂类来创建分层次的对象