什么是克隆模式
克隆模式: 也称之为 原型模式,顾名思义,就是创建一个实例对象的副本。创建副本的过程中,分为浅副本 和 深副本两种情况。
- 浅副本: 浅副本与原对象的某些属性共用一片内存接口,这是一种引用操作;
- 深副本: 深副本的所有属性内存与原对象的所有属性接口不一样,深副本的数据拷贝自原对象;
具体区别如下所示:
什么时候使用浅副本:如果资源有限(例如嵌入式系统)或者性能至关重要(例如高性能计算),使用浅复制 会更好,它可以实现数据共享、减少克隆对象的创建时间;
什么时候使用深副本:我们需要一个对象的完整副本。
为什么不选择重新创建一个实例对象,而选择克隆模式
- 如果重新创建一个实例对象,我们得到的只是一个原始状态的实例对象;
- 克隆模式获取的是当前状态下实例对象的副本;
- 两者操作获取的实例对象的状态信息是不一样的。
克隆模式的应用场景
- 当我们已有一个对象,并希望创建该对象的一个完整副本时,原型模式就派上用场了;
- 当我们想复制一个复杂对象时,使用原型模式会很方便;避免了重新创建对象,并对数据库进行多次查询操作;
克隆模式的例子
例子1:深度复制:deepcopy函数
Python 有专门的库函数来实现深度复制,例如:copy.deepcopy()
import copy
class A:
def __init__(self):
self.x = 18
self.msg = 'Hello'
class B(A):
def __init__(self):
super().__init__()
self.y = 34
def __str__(self):
return '{}, {}, {}'.format(self.x, self.msg, self.y)
if __name__ == '__main__':
b = B()
c = copy.deepcopy(b)
print([str(i) for i in (b, c)]) # 打印属性数据
print([i for i in (b, c)]) # 打印实例地址
输出的结果:
['18, Hello, 34', '18, Hello, 34']
[<__main__.B object at 0x7f4764bdbcf8>, <__main__.B object at 0x7f4764bdbda0>]
例子2:克隆书籍
假设某本书第一版的信息包括:书名,作者,价格等。现在要对该书进行再版,新版和旧版之间绝大部分的信息都是一样的。一个很好的办法就是对第一版的信息进行克隆,在副本的基础之上进行修改。
定义书籍类
在下述代码中,Book类展示了一种有趣的技术可避免可伸缩构造器问题。在 __init__()
方法中,仅有三个形参是固定的: name、 authors、price
,但是使用 rest
变长列表,调用者能以关键词的形式(名称=值)传入更多的参数。 self.__dict__.update(rest)
一行将 rest
的内容添加到 Book
类的内部字典中,成为它的一部分。
代码中还使用了 OrderedDict
来强制元素有序。
**加载公共库**
import copy
from collections import OrderedDict
class Book:
def __init__(self, name, authors, price, **rest):
'''rest的例子有:出版商,长度,标签,出版日期'''
self.name = name
self.authors = authors
self.price = price # 单位为美元
self.__dict__.update(rest)
def __str__(self):
mylist = [] # 存储属性信息
# 使字典固定顺序
ordered = OrderedDict(sorted(self.__dict__.items()))
for i in ordered.keys():
mylist.append('{}: {}'.format(i, ordered[i]))
if i == 'price':
mylist.append('$')
mylist.append('\n')
return ''.join(mylist)
定义克隆类
Prototype
类实现了原型设计模式。 Prototype
类的核心是clone()
方法,该方法使用我们熟悉的copy.deepcopy()
函数来完成真正的克隆工作。但Prototype
类在支持克隆之外做了一点更多的事情,它包含了方法 register()
和 unregister()
,这两个方法用于在一个字典中追踪被克隆的对象。
# 克隆类
class Prototype:
def __init__(self):
self.objects = dict() # 存储对象
# 登记要克隆的对象
def register(self, identifier, obj):
"""
Params:
identifier:需要克隆对象的标识;
obj:需要克隆的对象
"""
self.objects[identifier] = obj
# 在克隆集合中删除指定对象
def unregister(self, identifier):
del self.objects[identifier]
# 返回克隆后的对象
def clone(self, identifier, **attr):
"""
Params:
- identifier:对象标识;
- **attr:需要被更新的属性以及属性数据
"""
found = self.objects.get(identifier) # 根据标识获取对象
if not found:
raise ValueError('Incorrect object identifier: {}'.format(identifier))
obj = copy.deepcopy(found) # clone 对象
obj.__dict__.update(attr) # 更新副本的参数
return obj
实现克隆操作
- 首先建立了一个书籍对象 b1;
- 把 b1 登记进入克隆实例对象中;
- 选择克隆书籍对象 b1;
- 对比 b1 和其副本的 id;
def main():
# 创建一个原始对象
b1 = Book('The C Programming Language', ('Brian W. Kernighan', 'Dennis M.Ritchie'), price=118, publisher='Prentice Hall',
length=228, publication_date='1978-02-22', tags=('C', 'programming', 'algorithms', 'data structures'))
prototype = Prototype()
cid = 'k&r-first'
prototype.register(cid, b1)
b2 = prototype.clone(cid, name='The C Programming Language(ANSI)', price=48.99,
length=274, publication_date='1988-04-01', edition=2)
# 显示克隆对象的信息
for i in (b1, b2):
print(i)
print('ID b1 : {} != ID b2 : {}'.format(id(b1), id(b2)))
if __name__ == '__main__':
main()
运行的结果如下:
结果:
authors: ('Brian W. Kernighan', 'Dennis M.Ritchie')
length: 228
name: The C Programming Language
price: 118$
publication_date: 1978-02-22
publisher: Prentice Hall
tags: ('C', 'programming', 'algorithms', 'data structures')
authors: ('Brian W. Kernighan', 'Dennis M.Ritchie')
edition: 2
length: 274
name: The C Programming Language(ANSI)
price: 48.99$
publication_date: 1988-04-01
publisher: Prentice Hall
tags: ('C', 'programming', 'algorithms', 'data structures')
ID b1 : 139993865162592 != ID b2 : 139993865255400
源码在这里;
参考
- 《精通Python设计模式》