《精通python設(shè)計(jì)模式》讀書(shū)筆記之——?jiǎng)?chuàng)建型設(shè)計(jì)模式

前言:

這幾天拜讀了“圖靈程序設(shè)計(jì)叢書(shū)”的《精通python設(shè)計(jì)模式》,個(gè)人感覺(jué)是一本不錯(cuò)的介紹python設(shè)計(jì)模式的專(zhuān)業(yè)書(shū)籍,本書(shū)介紹了16種設(shè)計(jì)模式,每種設(shè)計(jì)模式從基本簡(jiǎn)介、現(xiàn)實(shí)生活例子、軟件的例子、應(yīng)用案例、代碼實(shí)現(xiàn)五個(gè)方面一一介紹,上手簡(jiǎn)單,學(xué)習(xí)條例清晰。推薦閱讀!!!接下來(lái)就將我的讀書(shū)筆記整理在這。

一、設(shè)計(jì)模式簡(jiǎn)介:

設(shè)計(jì)模式重要的部分可能就是它的名稱(chēng)。給模式起名的好處是大家相互交流時(shí)有共同的詞匯。因此,如果你提交一些代碼進(jìn)行評(píng)審,同行評(píng)審者的反饋中提到“我認(rèn)為這個(gè)地方你可以使用一個(gè)策略模式來(lái)代替……”,即使你不知道或不記得策略模式是什么,也可以立即去查閱。
隨著編程語(yǔ)言的演進(jìn),一些設(shè)計(jì)模式(如單例)也隨之過(guò)時(shí),甚至成了反模式,另一些則被內(nèi)置在編程語(yǔ)言中(如迭代器模式)。另外,也有一些新的模式誕生(比如Borg/Monostate)

關(guān)于設(shè)計(jì)模式有一些誤解。第一個(gè)誤解是,一開(kāi)始寫(xiě)代碼就應(yīng)該使用設(shè)計(jì)模式。第二個(gè)誤解是設(shè)計(jì)模式應(yīng)隨處使用。這會(huì)導(dǎo)致方案很復(fù)雜,夾雜著多余的接口和分層,而其實(shí)往往一個(gè)更簡(jiǎn)單直接的方案就足夠了。設(shè)計(jì)模式并不是萬(wàn)能的,僅當(dāng)代碼確實(shí)存在壞味道、難以擴(kuò)展維護(hù)時(shí),才有使用的必要。

設(shè)計(jì)模式是被發(fā)現(xiàn),而不是被發(fā)明出來(lái)的

二、設(shè)計(jì)模式三大基本類(lèi)型

設(shè)計(jì)模式分三大部分:

  1. 第一部分介紹處理對(duì)象創(chuàng)建的設(shè)計(jì)模式,包括工廠(chǎng)模式、建造者模式、原型模式;
  2. 第二部分介紹處理一個(gè)系統(tǒng)中不同實(shí)體(類(lèi)、對(duì)象等)之間關(guān)系的設(shè)計(jì)模式, 包括外觀(guān)模式、享元模式等 ;
  3. 第三部分介紹處理系統(tǒng)實(shí)體之間通信的設(shè)計(jì)模式,包括責(zé)任鏈模式、觀(guān)察者模式等。

三、創(chuàng)建型設(shè)計(jì)模式

處理對(duì)象創(chuàng)建相關(guān)的問(wèn)題,目標(biāo)是當(dāng)直接創(chuàng)建對(duì)象(在Python中是通過(guò)_init_()函數(shù)實(shí)現(xiàn)的,)不太方便時(shí),提供更好的方式。

①. 工廠(chǎng)模式

簡(jiǎn)介:
在工廠(chǎng)設(shè)計(jì)模式中,客戶(hù)端可以請(qǐng)求一個(gè)對(duì)象,而無(wú)需知道這個(gè)對(duì)象來(lái)自哪里;也就是,使用哪個(gè)類(lèi)來(lái)生成這個(gè)對(duì)象。工廠(chǎng)背后的思想是簡(jiǎn)化對(duì)象的創(chuàng)建。與客戶(hù)端自己基于類(lèi)實(shí)例化直 接創(chuàng)建對(duì)象相比,基于一個(gè)中心化函數(shù)來(lái)實(shí)現(xiàn),更易于追蹤創(chuàng)建了哪些對(duì)象。通過(guò)將創(chuàng)建對(duì)象的代碼和使用對(duì)象的代碼解耦,工廠(chǎng)能夠降低應(yīng)用維護(hù)的復(fù)雜度。

工廠(chǎng)通常有兩種形式:
第一種是工廠(chǎng)方法(Factory Method),它是一個(gè)方法(或以地道的Python 術(shù)語(yǔ)來(lái)說(shuō),是一個(gè)函數(shù)),對(duì)不同的輸入?yún)?shù)返回不同的對(duì)象;

現(xiàn)實(shí)生活的例子:
現(xiàn)實(shí)中用到工廠(chǎng)方法模式思想的一個(gè)例子是塑料玩具制造。制造塑料玩具的壓塑粉都是一樣的,但使用不同的塑料模具就能產(chǎn)出不同的外形。比如,有一個(gè)工廠(chǎng)方法,輸入是目標(biāo)外形(鴨 子或小車(chē))的名稱(chēng),輸出則是要求的塑料外形。

軟件的例子:
Django框架使用工廠(chǎng)方法模式來(lái)創(chuàng)建表單字段。Django的forms模塊支持不同種類(lèi)字段(CharField、EmailField)的創(chuàng)建和定制(max_length、required)

應(yīng)用案例:
如果因?yàn)閼?yīng)用創(chuàng)建對(duì)象的代碼分布在多個(gè)不同的地方,而不是僅在一個(gè)函數(shù)方法中,你發(fā)現(xiàn)沒(méi)法跟蹤這些對(duì)象,那么應(yīng)該考慮使用工廠(chǎng)方法模式。工廠(chǎng)方法集中地在一個(gè)地方創(chuàng)建對(duì)象,使對(duì)象跟蹤變得更容易。注意,創(chuàng)建多個(gè)工廠(chǎng)方法也完全沒(méi)有問(wèn) 題,實(shí)踐中通常也這么做,對(duì)相似的對(duì)象創(chuàng)建進(jìn)行邏輯分組,每個(gè)工廠(chǎng)方法負(fù)責(zé)一個(gè)分組。例如,有一個(gè)工廠(chǎng)方法負(fù)責(zé)連接到不同的數(shù)據(jù)庫(kù)(MySQL、SQLite),另一個(gè)工廠(chǎng)方法負(fù)責(zé)創(chuàng)建要求的幾何對(duì)象(圓形、三角形)等,若需要將對(duì)象的創(chuàng)建和使用解耦,工廠(chǎng)方法也能派上用場(chǎng)。
.
另外一個(gè)值得一提的應(yīng)用案例與應(yīng)用性能及內(nèi)存使用相關(guān)。工廠(chǎng)方法可以在必要時(shí)創(chuàng)建新的對(duì)象,從而提高性能和內(nèi)存使用率。若直接實(shí)例化類(lèi)來(lái)創(chuàng)建對(duì)象,那么每次創(chuàng)建新對(duì)象就需要分配額外的內(nèi)存(除非這個(gè)類(lèi)內(nèi)部使用了緩存,一般情況下不會(huì)這 樣)。例如類(lèi)的兩次實(shí)例化的內(nèi)存地址不同

第二種是抽象工廠(chǎng)
第二種是抽象工廠(chǎng),它是一組用于創(chuàng)建一系列相關(guān)事物對(duì)象的工廠(chǎng)方法
抽象工廠(chǎng)設(shè)計(jì)模式是抽象方法的一種泛化。概括來(lái)說(shuō),一個(gè)抽象工廠(chǎng)是(邏輯上的)一組工廠(chǎng)方法,其中的每個(gè)工廠(chǎng)方法負(fù)責(zé)產(chǎn)生不同種類(lèi)的對(duì)象

現(xiàn)實(shí)生活的例子:
汽車(chē)制造業(yè)應(yīng)用了抽象工廠(chǎng)的思想。沖壓不同汽車(chē)模型的部件(車(chē)門(mén)、儀表盤(pán)、車(chē)篷、擋泥板及反光鏡等)所使用的機(jī)件是相同的。機(jī)件裝配起來(lái)的模型隨時(shí)可配置,且易于改變。

軟件的例子:
程序包django_factory是一個(gè)用于在測(cè)試中創(chuàng)建Django模型的抽象工廠(chǎng)實(shí)現(xiàn),可用來(lái)為支持測(cè)試專(zhuān)有屬性的模型創(chuàng)建實(shí)例。這能讓測(cè)試代碼的可讀性更高,且能避免共享不必要的代碼,故有其存在的價(jià)值

應(yīng)用案例:
為抽象工廠(chǎng)模式是工廠(chǎng)方法模式的一種泛化,所以它能提供相同的好處。這樣會(huì)產(chǎn)生一個(gè)問(wèn)題:我們?cè)趺粗篮螘r(shí)該使用工廠(chǎng)方法,何時(shí)又該使用抽象工廠(chǎng)?答案是, 通常一開(kāi)始時(shí)使用工廠(chǎng)方法,因?yàn)樗?jiǎn)單。如果后來(lái)發(fā)現(xiàn)應(yīng)用需要許多工廠(chǎng)方法,那么將創(chuàng)建 一系列對(duì)象的過(guò)程合并在一起更合理,從而最終引入抽象工廠(chǎng)。

工廠(chǎng)方法代碼實(shí)現(xiàn):

import xml.etree.ElementTree as etree
import json

class JSONConnector:
    """
    類(lèi)JSONConnector解析JSON文件,通過(guò)parsed_data()方法以一個(gè)字典(dict)的形式 返回?cái)?shù)據(jù)。
    修飾器property使parsed_data()顯得更像一個(gè)常規(guī)的變量,而不是一個(gè)方法
    """
    def __init__(self, filepath):
        print(444)
        self.data = None
        with open(filepath, mode='r', encoding='utf-8') as f:
            self.data = json.load(f)

    @property
    def parsed_data(self):
        return self.data

class XMLConnector:
    """
    類(lèi)XMLConnector解析XML文件,通過(guò)parsed_data()方法以xml.etree.Element列表的形式返回所有數(shù)據(jù)
    """
    def __init__(self, filepath):
        self.tree = etree.parse(filepath)

    @property
    def parsed_data(self):
        return self.tree

def connection_factory(filepath):
    """
    函數(shù)connection_factory是一個(gè)工廠(chǎng)方法,基于輸入文件路徑的擴(kuò)展名返回一個(gè) JSONConnector或XMLConnector的實(shí)例
    :param filepath:
    :return: 返回一個(gè) JSONConnector或XMLConnector的實(shí)例
    """
    print(filepath)
    if filepath.endswith("json"):
        connector = JSONConnector
    elif filepath.endswith("xml"):
        connector = XMLConnector
    else:
        raise ValueError('Cannot connect to {}'.format(filepath))
    return connector(filepath)

def connect_to(filepath):
    """
    函數(shù)connect_to()對(duì)connection_factory()進(jìn)行包裝,添加了異常處理
    :param filepath:
    :return:返回一個(gè)JSONConnector或XMLConnector的實(shí)例對(duì)象
    """
    factory = None
    try:
        factory = connection_factory(filepath)
    except ValueError as ve:
        print(ve)
    return factory

def main(filepath):
    # 函數(shù)main()演示如何使用工廠(chǎng)方法設(shè)計(jì)模式。第一部分是確認(rèn)異常處理是否有效
    data_factory = connect_to(filepath)
    # 第二部分是得到數(shù)據(jù)格式對(duì)應(yīng)的處理
    new_data = data_factory.parsed_data
    # 第三部分是根據(jù)不同數(shù)據(jù)格式做特定的處理方法
    if type(data_factory) == XMLConnector:
        liars = new_data.findall(".//{}[{}='{}']".format('person', 'lastName', 'Liar'))
        print('found: {} persons'.format(len(liars)))
        for liar in liars:
            print('first name: {}'.format(liar.find('firstName').text))
            print('last name: {}'.format(liar.find('lastName').text))
            [print('phone number ({})'.format(p.attrib['type']), p.text)
             for p in liar.find('phoneNumbers')]
    if type(data_factory) == JSONConnector:
        for donut in new_data:
            print('name: {}'.format(donut['name']))
            print('price: ${}'.format(donut['ppu']))
            [print('topping: {} {}'.format(t['id'], t['type'])) for t in donut['topping']]

if __name__ == '__main__':
    main("person.xml")

抽象工廠(chǎng)代碼實(shí)現(xiàn):

"""
我們正在創(chuàng)造一個(gè)游戲,或者想在應(yīng)用中包含一個(gè)迷你游戲讓用戶(hù)娛樂(lè)娛樂(lè)。我們希望至少包含兩個(gè)游戲,
一個(gè)面向孩子,一個(gè)面向成人。在運(yùn)行時(shí),基于用戶(hù)輸入,決定該創(chuàng)建哪個(gè)游戲并運(yùn)行。游戲的創(chuàng)建部分由一個(gè)抽象工廠(chǎng)維護(hù)
"""
class Frog:
    """
    孩子的游戲,我們將該游戲命名為FrogWorld。主人公是一只青蛙,喜歡吃蟲(chóng)子。每個(gè)英雄都需要一個(gè)好名字,在這個(gè)例子中,
    這個(gè)名字在運(yùn)行時(shí)由用戶(hù)給定。方法interact_with()用于描述青蛙與障礙物(比如,蟲(chóng)子、迷宮或其他青蛙)之間的交互
    """
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def interact_with(self, obstacle):
        print('{} the Frog encounters {} and {}!'.format(self, obstacle, obstacle.action()))

class Bug:
    """
    障礙物可以有多種,但對(duì)于我們的例子,可以?xún)H僅是蟲(chóng)子。當(dāng)青蛙遇到一只蟲(chóng)子,只支持一 種動(dòng)作,那就是吃掉它!
    """
    def __str__(self):
        return 'a bug'

    def action(self):
        return 'eats it'

class FrogWorld:
    """
    類(lèi)FrogWorld是一個(gè)抽象工廠(chǎng),其主要職責(zé)是創(chuàng)建游戲的主人公和障礙物。區(qū)分創(chuàng)建方法并使其名字通用(比如,make_character()
    和make_obstacle()),這讓我們可以動(dòng)態(tài)改變當(dāng)前激活的工廠(chǎng)(也因此改變了當(dāng)前激活的游戲),而無(wú)需進(jìn)行任何代碼變更。
    在一門(mén)靜態(tài)語(yǔ)言中, 抽象工廠(chǎng)是一個(gè)抽象類(lèi)/接口,具備一些空方法,但在Python中無(wú)需如此,因?yàn)轭?lèi)型是在運(yùn)行時(shí)檢測(cè)的
    """
    def __init__(self, name):
        print(self)
        self.player_name = name

    def __str__(self):
        return '\n\n\t------ Frog World -------'

    def make_character(self):
        return Frog(self.player_name)

    def make_obstacle(self):
        return Bug()

class Wizard:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def interact_with(self, obstacle):
        print('{} the Wizard battles against {} and {}!'.format(self, obstacle, obstacle.action()))

class Ork:
    def __str__(self):
        return 'an evil ork'

    def action(self):
        return 'kills it'

class WizardWorld:
    """
    WizardWorld游戲也類(lèi)似。在故事中唯一的區(qū)別是男巫戰(zhàn)怪獸(如獸人)而不是吃蟲(chóng)子!
    """
    def __init__(self, name):
        print(self)
        self.player_name = name

    def __str__(self):
        return '\n\n\t------ Wizard World -------'

    def make_character(self):
        return Wizard(self.player_name)

    def make_obstacle(self):
        return Ork()

class GameEnvironment:
    """
    類(lèi)GameEnvironment是我們游戲的主入口。它接受factory作為輸入,
    用其創(chuàng)建游戲的世界。方法play()則會(huì)啟動(dòng)hero和obstacle之間的交互
    """
    def __init__(self, factory):
        self.hero = factory.make_character()
        self.obstacle = factory.make_obstacle()

    def play(self):
        self.hero.interact_with(self.obstacle)

def validate_age(name):
    """
    函數(shù)validate_age()提示用戶(hù)提供一個(gè)有效的年齡。如果年齡無(wú)效,則會(huì)返回一個(gè)元組, 其第一個(gè)元素設(shè)置為False。
    如果年齡沒(méi)問(wèn)題,元素的第一個(gè)元素則設(shè)置為T(mén)rue,但我們真正關(guān) 心的是元素的第二個(gè)元素,也就是用戶(hù)提供的年齡
    :param name:
    :return: ()
    """
    try:
        age = input('Welcome {}. How old are you? '.format(name))
        age = int(age)
    except ValueError as err:
        print("Age {} is invalid, please try again...".format(age))
        return (False, age)
    return (True, age)

def main():
    """
    該函數(shù)請(qǐng)求用戶(hù)的姓名和年齡,并根據(jù)用戶(hù)的年齡決定該玩哪個(gè)游戲
    :return:
    """
    name = input("Hello. What's your name? ")
    valid_input = False
    while not valid_input:
        valid_input, age = validate_age(name)
    game = FrogWorld if age < 18 else WizardWorld
    environment = GameEnvironment(game(name))
    environment.play()

if __name__ == '__main__':
    main()

工廠(chǎng)模式總結(jié):
兩種模式都可以用于以下幾種場(chǎng)景:
(a)想要追蹤對(duì)象的創(chuàng)建時(shí),
(b)想要將對(duì)象的創(chuàng)建與使用解耦時(shí),
(c)想要優(yōu)化應(yīng)用的性能 和資源占用時(shí)。
.
工廠(chǎng)方法設(shè)計(jì)模式的實(shí)現(xiàn)是一個(gè)不屬于任何類(lèi)的單一函數(shù),負(fù)責(zé)單一種類(lèi)對(duì)象(一個(gè)形狀、 一個(gè)連接點(diǎn)或者其他對(duì)象)的創(chuàng)建。
.
抽象工廠(chǎng)設(shè)計(jì)模式的實(shí)現(xiàn)是同屬于單個(gè)類(lèi)的許多個(gè)工廠(chǎng)方法用于創(chuàng)建一系列種類(lèi)的相關(guān)對(duì)象(一輛車(chē)的部件、一個(gè)游戲的環(huán)境,或者其他對(duì)象)。

②.建造者模式

簡(jiǎn)介:
我們想要?jiǎng)?chuàng)建一個(gè)由多個(gè)部分構(gòu)成的對(duì)象,而且它的構(gòu)成需要一步接一步地完成。只有當(dāng)各個(gè)部分都創(chuàng)建好,這個(gè)對(duì)象才算是完整的。這正是建造者設(shè)計(jì)模式(Builder design pattern)的用武之地。建造者模式將一個(gè)復(fù)雜對(duì)象的構(gòu)造過(guò)程與其表現(xiàn)分離,這樣,同一個(gè)構(gòu)造 過(guò)程可用于創(chuàng)建多個(gè)不同的表現(xiàn)。

實(shí)際例子:
HTML頁(yè)面生成問(wèn)題可以使用建造者模式來(lái)解決。該模式中,有兩個(gè)參與者:建造者(builder)和指揮者(director)。建造者負(fù)責(zé)創(chuàng)建復(fù)雜對(duì)象的各個(gè)組成部分。在HTML例子中,這些組成部分是頁(yè)面標(biāo)題、文本標(biāo)題、內(nèi)容主體及頁(yè)腳。指揮者使用一個(gè)建造者實(shí)例控制建造的過(guò)程。對(duì)于 HTML示例,這是指調(diào)用建造者的函數(shù)設(shè)置頁(yè)面標(biāo)題、文本標(biāo)題等。使用不同的建造者實(shí)例讓我們可以創(chuàng)建不同的HTML頁(yè)面,而無(wú)需變更指揮者的代碼。

現(xiàn)實(shí)生活的例子:
快餐店使用的就是建造者設(shè)計(jì)模式。

軟件的例子:
django-widgy是一個(gè) Django的第三方樹(shù)編輯器擴(kuò)展,可用作內(nèi)容管理系統(tǒng)(Content Management System,CMS)。它 包含一個(gè)網(wǎng)頁(yè)構(gòu)建器,用來(lái)創(chuàng)建具有不同布局的HTML頁(yè)面
.
django-query-builder是另一個(gè)基于建造者模式的Django第三方擴(kuò)展庫(kù),該擴(kuò)展庫(kù)可用于動(dòng)態(tài)地構(gòu)建SQL查詢(xún)。使用它,我們能夠控制一個(gè)查詢(xún)的方方面面,并能創(chuàng)建不同種類(lèi)的查詢(xún),從簡(jiǎn) 單的到非常復(fù)雜的都可以

應(yīng)用場(chǎng)景:
如果我們知道一個(gè)對(duì)象必須經(jīng)過(guò)多個(gè)步驟來(lái)創(chuàng)建,并且要求同一個(gè)構(gòu)造過(guò)程可以產(chǎn)生不同的表現(xiàn),就可以使用建造者模式。這種需求存在于許多應(yīng)用中,例如頁(yè)面生成器(HTML頁(yè)面生成器之類(lèi))、文檔轉(zhuǎn)換器以及用戶(hù)界面(User Interface, UI)表單創(chuàng)建工具

代碼實(shí)現(xiàn)

工廠(chǎng)模式買(mǎi)電腦
MINI14 = '1.4GHz Mac mini'

class AppleFactory:
    """
    工廠(chǎng)模式買(mǎi)電腦
    """
    class MacMini14:
        def __init__(self):
            self.memory = 4  # 單位為GB
            self.hdd = 500  # 單位為GB
            self.gpu = 'Intel HD Graphics 5000'

        def __str__(self):
            info = ('Model: {}'.format(MINI14), 'Memory: {}GB'.format(self.memory),
                    'Hard Disk: {}GB'.format(self.hdd), 'Graphics Card: {}'.format(self.gpu))
            return '\n'.join(info)

    def build_computer(self, model):
        if (model == MINI14):
            return self.MacMini14()
        else:
            print("I dont't know how to build {}".format(model))

if __name__ == '__main__':
    afac = AppleFactory()
    mac_mini = afac.build_computer(MINI14)
    print(mac_mini)
建造者模式定制電腦

class Computer:
    def __init__(self, serial_number):
        self.serial = serial_number
        self.memory = None  # 單位為GB
        self.hdd = None  # 單位為GB
        self.gpu = None

    def __str__(self):
        info = ('Memory: {}GB'.format(self.memory), 'Hard Disk: {}GB'
                .format(self.hdd), 'Graphics Card: {}'.format(self.gpu))
        return '\n'.join(info)

class ComputerBuilder:
    def __init__(self):
        self.computer = Computer('AG23385193')

    def configure_memory(self, amount):
        self.computer.memory = amount

    def configure_hdd(self, amount):
        self.computer.hdd = amount

    def configure_gpu(self, gpu_model):
        self.computer.gpu = gpu_model

class HardwareEngineer:
    def __init__(self):
        self.builder = None

    def construct_computer(self, memory, hdd, gpu):
        self.builder = ComputerBuilder()
        [
            i for i in (self.builder.configure_memory(memory),
                        self.builder.configure_hdd(hdd),
                        self.builder.configure_gpu(gpu)
                        )
        ]

    @property
    def computer(self):
        return self.builder.computer

def main():
    engineer = HardwareEngineer()
    engineer.construct_computer(hdd=500, memory=8, gpu='GeForce GTX 650 Ti')
    computer = engineer.computer
    print(computer)

if __name__ == '__main__':
    main()

工廠(chǎng)模式和建造者模式的的區(qū)別:
在工廠(chǎng)模式下,會(huì)立即返回一個(gè)創(chuàng)建好的對(duì)象;而在建造者模式下,僅在需要時(shí)客戶(hù)端代碼才顯式地請(qǐng)求指揮者返回最終的對(duì)象。
.
簡(jiǎn)單明了的例子:
買(mǎi)電腦的例子也許有助于區(qū)分建造者模式和工廠(chǎng)模式。
假設(shè)你想購(gòu)買(mǎi)一臺(tái)新電腦,如果決定購(gòu)買(mǎi)一臺(tái)特定的預(yù)配置的電腦型號(hào),則是在使用工廠(chǎng)模式。
假設(shè)你想定制一臺(tái)新電腦,使用的即是建造者模式。你是指揮者,向制造商(建造者)提供指令說(shuō)明心中理想的電腦規(guī)格。

建造者模式的總結(jié):
我們學(xué)習(xí)了如何使用建造者設(shè)計(jì)模式。可以在工廠(chǎng)模式(工廠(chǎng)方法或抽象工廠(chǎng))不適用的一些場(chǎng)景中使用建造者模式創(chuàng)建對(duì)象。在以下幾種情況下,與工廠(chǎng)模式相比,建造者模式是更好的選擇。
1. 想要?jiǎng)?chuàng)建一個(gè)復(fù)雜對(duì)象(對(duì)象由多個(gè)部分構(gòu)成,且對(duì)象的創(chuàng)建要經(jīng)過(guò)多個(gè)不同的步驟, 這些步驟也許還需遵從特定的順序)。
2. 要求一個(gè)對(duì)象能有不同的表現(xiàn),并希望將對(duì)象的構(gòu)造與表現(xiàn)解耦。
3. 想要在某個(gè)時(shí)間點(diǎn)創(chuàng)建對(duì)象,但在稍后的時(shí)間點(diǎn)再訪(fǎng)問(wèn)

③.原型模式

簡(jiǎn)介:
原型設(shè)計(jì)模式(Prototype design pattern)幫助我們創(chuàng)建對(duì)象的克隆,其最簡(jiǎn)單的形式就是一個(gè)clone函數(shù),接受一個(gè)對(duì)象作為輸入?yún)?shù),返回輸入對(duì)象的一個(gè)副本。在Python中,這可以使用copy.deepcopy()函數(shù)來(lái)完成。

現(xiàn)實(shí)生活的例子:
原型設(shè)計(jì)模式無(wú)非就是克隆一個(gè)對(duì)象。有絲分裂,即細(xì)胞分裂的過(guò)程,是生物克隆的一個(gè)例子。
另一個(gè)著名的(人工)克隆例子是多利羊

軟件的例子:
很多Python應(yīng)用都使用了原型模式,但幾乎都不稱(chēng)之為原型模式,因?yàn)閷?duì)象克隆是編程語(yǔ)言的一個(gè)內(nèi)置特性。
.
可視化工具套件(Visualization Toolkit,VTK)是原型模式的一個(gè)應(yīng)用。VTK是一個(gè)開(kāi)源的跨平臺(tái)系統(tǒng),用于三維計(jì)算機(jī)圖形/圖片處理以及可視化。VTK使用原型模式來(lái)創(chuàng)建幾何元素(比如,點(diǎn)、線(xiàn)、六面體等)的克隆。
.
music21也是使用原型模式的項(xiàng)目。根據(jù)該項(xiàng)目頁(yè)面所述,“music21是一組工具,幫助學(xué)者和其他積極的聽(tīng)眾快速簡(jiǎn)便地得到音樂(lè)相關(guān)問(wèn)題的答案”。 music21工具套件使用原型模式來(lái)復(fù)制音符和總譜。

應(yīng)用案例:
當(dāng)我們已有一個(gè)對(duì)象,并希望創(chuàng)建該對(duì)象的一個(gè)完整副本時(shí),原型模式就派上用場(chǎng)了。在我們知道對(duì)象的某些部分會(huì)被變更但又希望保持原有對(duì)象不變之時(shí),通常需要對(duì)象的一個(gè)副本。在這樣的案例中,重新創(chuàng)建原有對(duì)象是沒(méi)有意義的。
.
另一個(gè)案例是,當(dāng)我們想復(fù)制一個(gè)復(fù)雜對(duì)象時(shí),使用原型模式會(huì)很方便。對(duì)于復(fù)制復(fù)雜對(duì)象,我們可以將對(duì)象當(dāng)作是從數(shù)據(jù)庫(kù)中獲取的,并引用其他一些也是從數(shù)據(jù)庫(kù)中獲取的對(duì)象。若通過(guò)多次重復(fù)查詢(xún)數(shù)據(jù)來(lái)創(chuàng)建一個(gè)對(duì)象,則要做很多工作。在這種場(chǎng)景下使用原型模式要方便得多。

代碼實(shí)現(xiàn)

from collections import OrderedDict
import copy

class Book:
    """
    Book類(lèi)展示了一種有趣的技術(shù)可避免可伸縮構(gòu)造器問(wèn)題。在__init__() 方法中,僅有三個(gè)形參是固定的:
    name、authors和price,但是使用rest變長(zhǎng)列表,調(diào)用者 能以關(guān)鍵詞的形式(名稱(chēng)=值)傳入更多的參數(shù)。
    self.__dict__.update(rest)一行將rest 的內(nèi)容添加到Book類(lèi)的內(nèi)部字典中,成為它的一部分。
    """

    def __init__(self, name, authors, price, **rest):
        '''rest的例子有: 出版商、長(zhǎng)度、 標(biāo)簽、出版日期等等'''
        self.name = name
        self.authors = authors
        self.price = price  # 單位為美元
        self.__dict__.update(rest)

    def __str__(self):
        """
        我們并不知道所有被添加參數(shù)的名稱(chēng),但又需要訪(fǎng)問(wèn)內(nèi)部字典將這些參數(shù) 應(yīng)用到__str__()中,并且字典的內(nèi)容并不遵循
        任何特定的順序,所以使用一個(gè)OrderedDict來(lái)強(qiáng)制元素有序,否則,每次程序執(zhí)行都會(huì)產(chǎn)生不同的輸出。
        :return: str
        """
        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)

class Prototype:
    """
    Prototype類(lèi)實(shí)現(xiàn)了原型設(shè)計(jì)模式。Prototype類(lèi)的核心是clone()方法,該方法使用我們 熟悉的copy.deepcopy()
    函數(shù)來(lái)完成真正的克隆工作。但Prototype類(lèi)在支持克隆之外做了一 點(diǎn)更多的事情,它包含了方法register()和unregister(),
    這兩個(gè)方法用于在一個(gè)字典中追 蹤被克隆的對(duì)象。注意這僅是一個(gè)方便之舉,并非必需。
    """

    def __init__(self):
        self.objects = dict()

    def register(self, identifier, obj):
        self.objects[identifier] = obj

    def unregister(self, identifier):
        del self.objects[identifier]

    def clone(self, identifier, **attr):
        """
        clone()方法和Book類(lèi)中的__str__使用了相同的技巧,但這次是因?yàn)閯e的原因。使 用變長(zhǎng)列表attr,
        我們可以?xún)H傳遞那些在克隆一個(gè)對(duì)象時(shí)真正需要變更的屬性變量
        :param identifier:版本號(hào)
        :param attr: 新添加的屬性
        :return: obj
        """
        found = self.objects.get(identifier)

        if not found:
            raise ValueError('Incorrect object identifier: {}'.format(identifier))
        obj = copy.deepcopy(found)
        obj.__dict__.update(attr)
        return obj

def main():
    """
    main()函數(shù)以實(shí)踐的方式展示了本節(jié)開(kāi)頭提到的《C程序設(shè)計(jì)語(yǔ)言》一書(shū)克隆的例子。克隆 該書(shū)的第一個(gè)版本來(lái)創(chuàng)建第二個(gè)版本,
    我們僅需要傳遞已有參數(shù)中被修改參數(shù)的值,但也可以傳遞額外的參數(shù)。在這個(gè)案例中,edition就是一個(gè)新參數(shù),
    在書(shū)的第一個(gè)版本中并不需要,但對(duì) 于克隆版本卻是很有用的信息。
    :return:
    """
    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()

原型模式的總結(jié):
深副本與淺副本。
深副本:原始對(duì)象的所有數(shù)據(jù)都被簡(jiǎn)單地復(fù)制到克隆對(duì)象中,沒(méi)有例外。
淺副本:則依賴(lài)引用,我們可以引入數(shù)據(jù)共享和寫(xiě)時(shí)復(fù)制一類(lèi)的技術(shù)來(lái)優(yōu)化性能(例如,減小克隆對(duì)象的創(chuàng)建時(shí)間)和內(nèi)存使用。如果可用資源有限(例如,嵌入式系統(tǒng))或性能至關(guān)重 要(例如,高性能計(jì)算),那么使用淺副本可能更佳。
.
在Python中,可以使用copy.copy函數(shù)進(jìn)行淺復(fù)制。以下內(nèi)容引用自Python官方文檔,說(shuō)明了淺副本copy.copy和深副本(copy.deepcopy())之間的區(qū)別。
淺副本構(gòu)造一個(gè)新的復(fù)合對(duì)象后,(會(huì)盡可能地)將在原始對(duì)象中找到的對(duì)象的引用插入新對(duì)象中。
深副本構(gòu)造一個(gè)新的復(fù)合對(duì)象后,會(huì)遞歸地將在原始對(duì)象中找到的對(duì)象的副本插入新對(duì)象中。
.
原型模式用于創(chuàng)建對(duì)象的完全副本。確切地說(shuō),創(chuàng)建一個(gè)對(duì)象的副本可以指代以下兩件事情。
第一種:當(dāng)創(chuàng)建一個(gè)淺副本時(shí),副本依賴(lài)引用
第二種:當(dāng)創(chuàng)建一個(gè)深副本時(shí),副本復(fù)制所有東西
.
第一種情況中:
我們關(guān)注提升應(yīng)用性能和優(yōu)化內(nèi)存使用,在對(duì)象之間引入數(shù)據(jù)共享,但需要小心地修改數(shù)據(jù),因?yàn)樗凶兏鼘?duì)所有副本都是可見(jiàn)的。淺副本在本章中沒(méi)有過(guò)多介紹,但也許 你會(huì)想試驗(yàn)一下。
第二種情況中:
我們希望能夠?qū)σ粋€(gè)副本進(jìn)行更改而不會(huì)影響其他對(duì)象。對(duì)于我們之前看到的蛋糕食譜示例這類(lèi)案例,這一特性是很有用的。這里不會(huì)進(jìn)行數(shù)據(jù)共享,所以需要關(guān)注因?qū)ο?克隆而引入的資源耗用問(wèn)題。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀(guān)點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 設(shè)計(jì)模式概述 在學(xué)習(xí)面向?qū)ο笃叽笤O(shè)計(jì)原則時(shí)需要注意以下幾點(diǎn):a) 高內(nèi)聚、低耦合和單一職能的“沖突”實(shí)際上,這兩者...
    彥幀閱讀 3,772評(píng)論 0 14
  • 設(shè)計(jì)模式匯總 一、基礎(chǔ)知識(shí) 1. 設(shè)計(jì)模式概述 定義:設(shè)計(jì)模式(Design Pattern)是一套被反復(fù)使用、多...
    MinoyJet閱讀 3,970評(píng)論 1 15
  • 下面總結(jié)設(shè)計(jì)模式中的創(chuàng)建型模式: 1.簡(jiǎn)單工廠(chǎng)模式 簡(jiǎn)單工廠(chǎng)不是設(shè)計(jì)模式,更像是一種編程習(xí)慣。它把實(shí)例化的操作單獨(dú)...
    Steven1997閱讀 1,270評(píng)論 0 0
  • 美麗的女孩 這個(gè)世界疼你都來(lái)不及了 別為情愛(ài)而迷茫 你有你的曙光 如果說(shuō) 空間是女性 是容納和孕育的可能 時(shí)間是男...
    以川閱讀 323評(píng)論 0 1
  • 《窮查理寶典》里面提到的芒格的思想之一“時(shí)刻警惕自己那些堅(jiān)信不疑的知識(shí),時(shí)刻保持懷疑精神”芒格自己也是每天堅(jiān)持學(xué)習(xí)...
    隨而去吧閱讀 214評(píng)論 0 0