Python設(shè)計(jì)模式之抽象工廠模式

抽象工廠

抽象工廠設(shè)計(jì)模式是抽象方法的一種泛化。概括來(lái)說(shuō),一個(gè)抽象工廠是(邏輯上的)一組工廠方法,其中的每個(gè)工廠方法負(fù)責(zé)產(chǎn)生不同種類的對(duì)象。

真實(shí)的例子

抽象工廠用在車輛織造中。相同的機(jī)械裝置用來(lái)沖壓不同的車輛模型的部件(門,面板,車身罩體,擋泥板,以及各種鏡子)。聚合了不同機(jī)械裝置的模型是可配置的,而且在任何時(shí)候都可以輕松變更。在這個(gè)鏈接中我們可以看到一個(gè)車輛制造的抽象工廠的例子。

如下所示的示例可以作為抽象工廠實(shí)現(xiàn)的參考:

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# http://ginstrom.com/scribbles/2007/10/08/design-patterns-python-style/

"""Implementation of the abstract factory pattern"""

import random

class PetShop:

    """A pet shop"""

    def __init__(self, animal_factory=None):
        """pet_factory is our abstract factory.  We can set it at will."""

        self.pet_factory = animal_factory

    def show_pet(self):
        """Creates and shows a pet using the abstract factory"""

        pet = self.pet_factory.get_pet()
        print("We have a lovely {}".format(pet))
        print("It says {}".format(pet.speak()))
        print("We also have {}".format(self.pet_factory.get_food()))

# Stuff that our factory makes

class Dog:

    def speak(self):
        return "woof"

    def __str__(self):
        return "Dog"


class Cat:

    def speak(self):
        return "meow"

    def __str__(self):
        return "Cat"


# Factory classes

class DogFactory:

    def get_pet(self):
        return Dog()

    def get_food(self):
        return "dog food"


class CatFactory:

    def get_pet(self):
        return Cat()

    def get_food(self):
        return "cat food"


# Create the proper family
def get_factory():
    """Let's be dynamic!"""
    return random.choice([DogFactory, CatFactory])()


# Show pets with various factories

for i in range(3):
    shop = PetShop(get_factory())
    shop.show_pet()
    print("=" * 20)

### OUTPUT ###
# We have a lovely Dog
# It says woof
# We also have dog food
# ====================
# We have a lovely Dog
# It says woof
# We also have dog food
# ====================
# We have a lovely Cat
# It says meow
# We also have cat food
# ====================


軟件例子

程序包django_factory是一個(gè)用于在測(cè)試中創(chuàng)建Django模型的抽象工廠實(shí)現(xiàn),可用來(lái)為支持測(cè)試專有屬性的模型創(chuàng)建實(shí)例。這能讓測(cè)試代碼的可讀性更高,且能避免共享不必要的代碼,故有其存在的價(jià)值(請(qǐng)參考網(wǎng)頁(yè)[t.cn/RqBBvcw])。

使用案例

因?yàn)槌橄蠊つJ绞枪S方法模式的歸納,它具有同樣的優(yōu)點(diǎn):使得追蹤一個(gè)對(duì)象創(chuàng)建更容易,它讓對(duì)象的使用和創(chuàng)建分離開(kāi),并且給我們內(nèi)存使用以及應(yīng)用的性能提高的可能

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

抽象工廠有一個(gè)優(yōu)點(diǎn),在使用工廠方法時(shí)從用戶視角通常是看不到的,那就是抽象工廠能夠通過(guò)改變激活的工廠方法動(dòng)態(tài)地(運(yùn)行時(shí))改變應(yīng)用行為。一個(gè)經(jīng)典例子是能夠讓用戶在使用應(yīng)用時(shí)改變應(yīng)用的觀感(比如,Apple風(fēng)格和Windows風(fēng)格等),而不需要終止應(yīng)用然后重新啟動(dòng)。

實(shí)現(xiàn)

為演示抽象工廠模式,我將重新使用Python 3 Patterns & Idioms(Bruce Eckel著)一書(shū)中的一個(gè)例子,它是我個(gè)人最喜歡的例子之一。想象一下,我們正在創(chuàng)造一個(gè)游戲,或者想在應(yīng)用中包含一個(gè)迷你游戲讓用戶娛樂(lè)娛樂(lè)。我們希望至少包含兩個(gè)游戲, 一個(gè)面向孩子,一個(gè)面向成人。在運(yùn)行時(shí),基于用戶輸入,決定該創(chuàng)建哪個(gè)游戲并運(yùn)行。游戲的創(chuàng)建部分由一個(gè)抽象工廠維護(hù)。

從孩子的游戲說(shuō)起,我們將該游戲命名為 FrogWorld。主人公是一只青蛙,喜歡吃蟲(chóng)子。每個(gè)英雄都需要一個(gè)好名字,在我們的例子中,這個(gè)名字在運(yùn)行時(shí)由用戶給定。方法 interact_with()用于描述青蛙與障礙物(比如,蟲(chóng)子、迷宮或其他青蛙)之間的交互,如下所示:

Class Frog:
    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()))

可以有很多種類的障礙,不過(guò)對(duì)于我們的這個(gè)例子來(lái)說(shuō)可以只有一個(gè)Bug。青蛙遇到一個(gè)蟲(chóng)子,只有一個(gè)行為被支持:吃掉它!

Class Bug:
    def __str__(self):
        return 'a bug'

    def action(self):
        return 'eats it'

FrogWorld類是一個(gè)抽象工廠。它的主要責(zé)任是游戲的主角和障礙物。保持創(chuàng)建方法的獨(dú)立和方法名稱的普通(例如,make_character(),make_obstacle())讓我們可以動(dòng)態(tài)的改變活動(dòng)工廠(以及活動(dòng)的游戲)而不設(shè)計(jì)任何的代碼變更。如下所示,在一個(gè)靜態(tài)類型語(yǔ)言中,抽象工廠可以是一個(gè)有著空方法的抽象類/接口,但是在Python中不要求這么做,因?yàn)轭愋驮谶\(yùn)行時(shí)才被檢查。

Class FrogWorld:
    def __init__(self, name):
        print(self)
        self.player_name = name

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

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

    def make_obstacle(self):
        return Bug()

游戲WizarWorld也類似。唯一的不同是巫師不吃蟲(chóng)子而是與半獸人這樣的怪物戰(zhàn)斗!

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

    def __str__(self):
        return self.name

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


Class Ork:
    def __str__(self):
        return 'an evial ork'

    def action(self):
        return 'kills it'



Class WizardWorld:
    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()


GameEnviroment是我們游戲的主要入口。它接受factory作為輸入,然后用它創(chuàng)建游戲的世界。play()方法發(fā)起創(chuàng)造的hero和obstacle之間的交互如下:

Class GameEnviroment:
        def __init__(self, factory):
            self.hero = factory.make_character()
            self.obstacle = factory.make_obstacle()

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

validate_age()函數(shù)提示用戶輸入一個(gè)有效的年齡。若年齡無(wú)效,則返回一個(gè)第一個(gè)元素設(shè)置為False的元組。如下所示,若輸入的年齡沒(méi)問(wèn)題,元組的第一個(gè)元素設(shè)置為True,以及我們所關(guān)心的該元組的第二個(gè)元素,即用戶給定的年齡:

def validate_age(name):
        try:
            age = input('Welcome {}. How old are you?'.format(name))
            age = int(age)
        except ValueError as err:
            print("Age {} is valid, plz try again...".format(age))
            return (False, age)
        return (True, age)

最后,尤其是main()函數(shù)的出現(xiàn)。它詢問(wèn)了用戶的名字和年齡,然后由用戶的年齡來(lái)決定哪一個(gè)游戲應(yīng)該被運(yùn)行:

def main():
        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
            enviroment = GameEnviroment(game(name))
            enviroment.play()

抽象工廠實(shí)現(xiàn)(abstrac_factory.py)的完整代碼如下:

class Frog:

    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:

    def __str__(self):
        return 'a bug'

    def action(self):
        return 'eats it'


class FrogWorld:

    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:

    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:

    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):
    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():
    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()

main()


# Hello. What's your name? Nick
# Welcome Nick. How old are you? 17


#   ------ Frog World -------
# Nick the Frog encounters a bug and eats it!


試著擴(kuò)展游戲使它更加復(fù)雜。你的思想有多遠(yuǎn)就可以走多遠(yuǎn):更多的障礙物,更多的敵人,以及任何你喜歡的東西。

Abstract Factory versus Factory Method

Let's look at the instances where we need to use the Abstract Factory or Factory Method.

Use the Factory Method pattern when there is a need to decouple a client from a particular product it uses. Use the Factory Method to relieve a client of the responsibility of creating and configuring instances of a product.

Use the Abstract Factory pattern when clients must be decoupled from the product classes. The Abstract Factory pattern can also enforce constraints specifying which classes must be used with others, creating independent families of objects.”

總結(jié)

本章中,我們學(xué)習(xí)了如何使用工廠方法和抽象工廠設(shè)計(jì)模式。兩種模式都可以用于以下幾種場(chǎng)景:(a)想要追蹤對(duì)象的創(chuàng)建時(shí),(b)想要將對(duì)象的創(chuàng)建與使用解耦時(shí),(c)想要優(yōu)化應(yīng)用的性能和資源占用時(shí)。場(chǎng)景(c)在本章中并未詳細(xì)說(shuō)明,你也許可以將其作為一個(gè)練習(xí)。

工廠方法設(shè)計(jì)模式的實(shí)現(xiàn)是一個(gè)不屬于任何類的單一函數(shù),負(fù)責(zé)單一種類對(duì)象(一個(gè)形狀、一個(gè)連接點(diǎn)或者其他對(duì)象)的創(chuàng)建。我們看到工廠方法是如何與玩具制造相關(guān)聯(lián)的,提到Django是如何將其用于創(chuàng)建不同表單字段的,并討論了其他可能的應(yīng)用案例。作為示例,我們實(shí)現(xiàn)了一個(gè)工廠方法,提供了訪問(wèn)XML和JSON文件的能力。

抽象工廠設(shè)計(jì)模式的實(shí)現(xiàn)是同屬于單個(gè)類的許多個(gè)工廠方法用于創(chuàng)建一系列種類的相關(guān)對(duì)象(一輛車的部件、一個(gè)游戲的環(huán)境,或者其他對(duì)象)。我們提到抽象工廠如何與汽車制造業(yè)相關(guān)聯(lián),Django程序包django_factory是如何利用抽象工廠創(chuàng)建干凈的測(cè)試用例,并學(xué)習(xí)了抽象工廠的應(yīng)用案例。作為抽象工廠實(shí)現(xiàn)的示例,我們完成了一個(gè)迷你游戲,演示了如何在單個(gè)類中使用多個(gè)相關(guān)工廠。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

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