什麼是克隆模式
克隆模式: 也稱之爲 原型模式,顧名思義,就是創建一個實例對象的副本。創建副本的過程中,分爲淺副本 和 深副本兩種情況。
- 淺副本: 淺副本與原對象的某些屬性共用一片內存接口,這是一種引用操作;
- 深副本: 深副本的所有屬性內存與原對象的所有屬性接口不一樣,深副本的數據拷貝自原對象;
具體區別如下所示:
什麼時候使用淺副本:如果資源有限(例如嵌入式系統)或者性能至關重要(例如高性能計算),使用淺複製 會更好,它可以實現數據共享、減少克隆對象的創建時間;
什麼時候使用深副本:我們需要一個對象的完整副本。
爲什麼不選擇重新創建一個實例對象,而選擇克隆模式
- 如果重新創建一個實例對象,我們得到的只是一個原始狀態的實例對象;
- 克隆模式獲取的是當前狀態下實例對象的副本;
- 兩者操作獲取的實例對象的狀態信息是不一樣的。
克隆模式的應用場景
- 當我們已有一個對象,並希望創建該對象的一個完整副本時,原型模式就派上用場了;
- 當我們想複製一個複雜對象時,使用原型模式會很方便;避免了重新創建對象,並對數據庫進行多次查詢操作;
克隆模式的例子
例子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設計模式》