通俗 Python 設計模式——原型設計模式
從本文開始,將會寫一系列關於 Python 設計模式 通俗講解的文章,主要參考《精通Python設計模式》一書,順序可能與原書目錄順序有所不同,因為我本身也是一邊學習一邊總結。本系列文章的主旨是剝開複雜的理論外殼,用最通俗的方法來講解 Python 設計模式。並且,通過修改代碼為更簡潔的形式,來更好的破除專業辭彙迷信。
《Python 設計模式》一書將各種設計模式做了一個簡單分類:- 創建型模式
- 結構型模式
- 行為型模式
今天要討論的是第一種——創建型模式——中的原型設計模式。
根據書上的說法,原型模式的作用是:當我們已有一個對象,並希望創建該對象的一個完整副本時,原型模式就派上用場了。在我們知道對象的某些部分會被變更但又希望保持原有對象不變之時,通常需要對象的一個副本。在這樣的案例中,重新創建原有對象是沒有意義的(請參考網頁[ Mitotic division ])。
另一個案例是,當我們想複製一個複雜對象時,使用原型模式會很方便。對於複製複雜對象,我們可以將對象當作是從資料庫中獲取的,並引用其他一些也是從資料庫中獲取的對象。若通過多次重複查詢數據來創建一個對象,則要做很多工作。在這種場景下使用原型模式要方便得多。
這一段描述太抽象,其實用最簡單的話來說就是:
看起來好像還是很複雜?無所謂,把每種情況中,看不懂的 | 第一個 | 多次出現的 | 單詞改為A,看不懂的 | 第二個 | 多次出現的 | 單詞改為 B,就明白了。好,我們現在把概念用最簡單的話梳理清楚了,那麼我們來看看代碼實現。書上給出了這樣一個例子:第一種情況,你有一個 ApplePen,想要一個一模一樣的 ApplePen,可以直接製作一個 ApplePen 的副本。
第二種情況,你有一個 ApplePen,想要一個 PineapplePen,可以通過製作一個 ApplePen 的副本,然後修改這個副本為 PineapplePen 的方式來達成目的。第三種情況,你有一個非常複雜的 PenPineappleApplePen,然後你需要一個PineapplePenApplePen,多次操作可能會非常麻煩,那麼可以以 PenPineappleApplePen 為原型,創建一個副本 PenPineappleApplePen,再通過簡單的幾步就能將其修改為 PineapplePenApplePen。(其實一般情況下與第二種情況沒差)
這裡其實可以看做我們總結出來的第二種情況,即:在有了原型對象的情況下,如何通過使用原型設計模式的編碼,來得到新的對象。我們先看看書上給出的示例代碼,原書代碼中沒有注釋,我嘗試補充了一些注釋,也許會有一定的錯漏:一本書,第一版出版了。10年後,第二版出版了,有一定的修改。如何使用原型模式創建一個展示圖書信息的應用?
import copynfrom collections import OrderedDictnnclass Book:n def __init__(self, name, authors, price, **rest):n rest的例子有:出版商、長度、標籤、出版日期n self.name = namen self.authors = authorsn self.price = pricen self.__dict__.update(rest) # 添加其他額外屬性nn def __str__(self):n mylist = []n ordered = OrderedDict(sorted(self.__dict__.items()))n for i in ordered.keys():n mylist.append({}: {}.format(i, ordered[i]))n if i == price:n mylist.append($)n mylist.append(n)n return .join(mylist)nnclass Prototype:n def __init__(self):n self.objects = dict() # 初始化一個原型列表nn def register(self, identifier, obj):n # 在原型列表中註冊原型對象n self.objects[identifier] = objnn def unregister(self, identifier):n # 從原型列表中刪除原型對象n del self.objects[identifier]nn def clone(self, identifier, **attr):n # 根據 identifier 在原型列表中查找原型對象並克隆n found = self.objects.get(identifier)n if not found:n raise ValueError(Incorrect object identifier: {}.format(identifier))n obj = copy.deepcopy(found)n obj.__dict__.update(attr) # 用新的屬性值替換原型對象中的對應屬性n return objnndef main():n b1 = Book(The C Programming Language, (Brian W. Kernighan, Dennis M.Ritchie),n price=118, publisher=Prentice Hall, length=228, publication_date=1978-02-22,n tags=(C, programming, algorithms, data structures))nn prototype = Prototype()n cid = k&r-firstn prototype.register(cid, b1)n b2 = prototype.clone(cid, name=The C Programming Language(ANSI), price=48.99, length=274, publication_date=1988-04-01, edition=2)nn for i in (b1, b2):n print(i)n print("ID b1 : {} != ID b2 : {}".format(id(b1), id(b2)))nnif __name__ == __main__:n main()n
我們將這段代碼保存為 prototype.py,在控制台運行,可以得到如下輸出:
authors: (Brian W. Kernighan, Dennis M.Ritchie)nlength: 228nname: The C Programming Languagenprice: 118$npublication_date: 1978-02-22npublisher: Prentice Hallntags: (C, programming, algorithms, data structures)nnauthors: (Brian W. Kernighan, Dennis M.Ritchie)nedition: 2nlength: 274nname: The C Programming Language(ANSI)nprice: 48.99$nnpublication_date: 1988-04-01npublisher: Prentice Hallntags: (C, programming, algorithms, data structures)nnID b1 : 2378797084512 != ID b2 : 2378796684008n
def main():n b1 = Book(The C Programming Language,n (Brian W. Kernighan, Dennis M.Ritchie),n price=118,n publisher=Prentice Hall,n length=228,n publication_date=1978-02-22,n tags=(C, programming, algorithms, data structures))nn # 這裡我們徹底拋棄之前的原型設計模式的寫法nn b2 = copy.deepcopy(b1)n b2.name = The C Programming Language(ANSI)n b2.price = 48.99n b2.length = 274n b2.publication_date = 1988-04-01n b2.edition = 2nn for i in (b1, b2):n print(i)n print("ID b1 : {} != ID b2 : {}".format(id(b1), id(b2)))n
同樣的內容,替換後再次運行,輸出結果幾乎一模一樣。所以,我們現在明白了,得益於 Python 的靈活性,其實在一般情況下沒必要搞得那麼費勁,用最簡單粗暴的方式就可以達成我們想要的目標。當然,這裡並不是說原型模式就沒用,各位根據情況,靈活處理就好。
推薦閱讀:
※使用文本挖掘實現站點個性化推薦
※Scrapy爬蟲框架教程(二)-- 爬取豆瓣電影TOP250
※如何去尋找網路爬蟲的需求?
※為什麼 Python、Ruby 等語言棄用了自增運算符?
※用半年的時間來開發一個新網站,應該選 PHP 還是 Python?