文本是《设计模式(共12篇)》专题的第 5 篇。阅读本文前,建议先阅读前面的文章:
原型模式详解
1️⃣ 概念
定义: 指原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象
特点: 不需要知道任何的创建细节,并且不调用构造函数
类型: 创建型
2️⃣ 适用场景
- 类初始化消耗太多资源
 - 直接创建一个对象需要非常繁琐的过程(数据准备、访问权限等)
 - 构造函数比较复杂
 - 循环体中生产大量的对象时
 
3️⃣ 优点
- 原型模式性能比直接创建一个对象性能高
 - 简化创建过程
 
4️⃣ 缺点
- 必须配备克隆方法
 - 对克隆复杂对象或者对克隆出的对象进行复杂改造时,容易引入风险
 - 深拷贝和浅拷贝要运用得当
 
5️⃣ 原型模式 Coding
① 创建一个发邮件的场景
import copy
class Mail:
    def __init__(self):
        self.name = None
        self.email_address = None
        self.content = None
        print("Mail Class Constructor")
    def get_name(self):
        return self.name
    def set_name(self, name):
        self.name = name
    def get_email_address(self):
        return self.email_address
    def set_email_address(self, email_address):
        self.email_address = email_address
    def get_content(self):
        return self.content
    def set_content(self, content):
        self.content = content
    def __str__(self):
        return f"Mail{{name='{self.name}', email_address='{self.email_address}', content='{self.content}'}}{id(self)}"
    def clone(self):
        print("clone mail object")
        return copy.copy(self)
② 创建 MailUtil 工具类
class MailUtil:
    @staticmethod
    def send_mail(mail):
        output_content = "向{0}同学,邮件地址:{1},邮件内容:{2}发送邮件成功"
        print(output_content.format(mail.get_name(), mail.get_email_address(), mail.get_content()))
    @staticmethod
    def save_origin_mail_record(mail):
        print(f"存储originMail记录,originMail:{mail.get_content()}")
③ 编写测试类
def test():
    mail = Mail()
    mail.set_content("初始化模板")
    print(f"初始化mail:{mail}")
    for i in range(10):
        mail_temp = mail.clone()
        mail_temp.set_name(f"姓名{i}")
        mail_temp.set_email_address(f"姓名{i}@test.com")
        mail_temp.set_content("恭喜您,此次活动中奖了")
        MailUtil.send_mail(mail_temp)
        print(f"克隆的mail_temp:{mail_temp}")
    MailUtil.save_origin_mail_record(mail)
if __name__ == "__main__":
    test()
6️⃣ UML类图

7️⃣ 深克隆与浅克隆
(1)浅克隆
① 创建 Pig 类
import copy
from datetime import datetime
class Pig:
    def __init__(self, name, birthday):
        self.name = name
        self.birthday = birthday
    def get_name(self):
        return self.name
    def set_name(self, name):
        self.name = name
    def get_birthday(self):
        return self.birthday
    def set_birthday(self, birthday):
        self.birthday = birthday
    def clone(self):
        return copy.copy(self)
    def __str__(self):
        return f"Pig{{name='{self.name}', birthday={self.birthday}}}{id(self)}"
② 编写测试类
def test():
    birthday = datetime.fromtimestamp(0)
    pig1 = Pig("佩奇", birthday)
    pig2 = pig1.clone()
    print(pig1)
    print(pig2)
    pig1.get_birthday().replace(year=2021, month=2, day=14)
    # 注意:datetime是不可变对象,需要重新赋值
    pig1.birthday = datetime.fromtimestamp(666666666)
    print(pig1)
    print(pig2)
if __name__ == "__main__":
    test()
分析结果:
从结果中可以看出,先打印的pig1与pig2的内容是一致的,在设置完生日以后打印的一组结果内容也是一致的,但是我们发现在我们设置完pig1的生日以后,后打印的一组内容的生日都发生了变化,这又是为什么呢?我们通过debug找到了答案:
通过分析我们找到了原因,pig1与pig2对象所使用的生日属性都是一个对象的,所以我们在修改完生日以后两个对象的内容都发生了变化(也就是说我们引用的克隆对象都是同一个对象,当我们修改被克隆对象的属性的时候,克隆出来的对象属性也会跟着发生变化);这就是浅克隆,同时默认的也是浅克隆。
(2)深克隆
从上边的结论中我们可以得出,默认的克隆方式是浅克隆,那么怎么实现深克隆呢?其实也简单,只要我们对对象的引用类型也添加克隆实现就可以解决了:
def clone(self):
    # 深克隆
    return copy.deepcopy(self)
通过使用copy.deepcopy()方法我们可以看出在我们进行了引用属性的深克隆以后,最后一组对象的内容已经发生了变化,同时debug的时候内存中引用的对象也发生了变化,效果已经达到了我们的预期。
注意: 在使用原型模式的时候一定要进行深克隆,否则可能会出现Bug
8️⃣ 原型模式破坏单例
① 使用 HungrySingleton
import copy
class HungrySingleton:
    _instance = None
    _initialized = False
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance
    def __init__(self):
        if HungrySingleton._initialized:
            raise RuntimeError("单例构造器禁止重复调用")
        HungrySingleton._initialized = True
    @classmethod
    def get_instance(cls):
        if cls._instance is None:
            cls._instance = cls()
        return cls._instance
    def clone(self):
        return copy.copy(self)
② 编写测试类
def test():
    hungry_singleton = HungrySingleton.get_instance()
    clone_hungry_singleton = hungry_singleton.clone()
    print(hungry_singleton)
    print(clone_hungry_singleton)
    print(f"是否为同一对象: {hungry_singleton is clone_hungry_singleton}")
if __name__ == "__main__":
    test()
可以看到通过使用clone我们依旧破坏了单例,那要如何才能解决这个问题呢?
解决方案
① 单例类不实现clone方法
② 单例类在重写clone方法时不使用默认的实现,将其修改为:
def clone(self):
    return self.get_instance()
可以看到通过以上的修改,再次运行测试类的时候两个对象就完全是一致的了。
9️⃣ 原型模式的实际应用
① Python标准库中原型模式的使用
list的copy方法实现:
# Python中的list支持浅拷贝
original_list = [1, 2, [3, 4]]
shallow_copy = original_list.copy()  # 或者使用 original_list[:]
deep_copy = copy.deepcopy(original_list)
print(f"原始列表: {original_list}")
print(f"浅拷贝: {shallow_copy}")
print(f"深拷贝: {deep_copy}")
# 修改嵌套列表
original_list[2].append(5)
print(f"修改后原始列表: {original_list}")
print(f"修改后浅拷贝: {shallow_copy}")  # 会受到影响
print(f"修改后深拷贝: {deep_copy}")    # 不会受到影响
dict的copy方法实现:
import copy
# Python中的dict也支持拷贝
original_dict = {'a': 1, 'b': {'c': 2}}
shallow_copy = original_dict.copy()
deep_copy = copy.deepcopy(original_dict)
print(f"原始字典: {original_dict}")
print(f"浅拷贝: {shallow_copy}")
print(f"深拷贝: {deep_copy}")
# 修改嵌套字典
original_dict['b']['c'] = 3
print(f"修改后原始字典: {original_dict}")
print(f"修改后浅拷贝: {shallow_copy}")  # 会受到影响
print(f"修改后深拷贝: {deep_copy}")    # 不会受到影响
同理Python标准库中的很多容器类型都实现了copy方法,也就是说它们也应用了原型模式。
您已阅读完《设计模式(共12篇)》专题的第 5 篇。请继续阅读该专题下面的文章:
