[Python]工廠模式

介紹

工廠模式是23種設(shè)計(jì)模式中最常用的模式之一,它又可以細(xì)分成簡單工廠,工廠方法和抽象工廠。光憑概念很難理解,下面我會(huì)通過一個(gè)場(chǎng)景來介紹。

??情景模擬

爸爸是一個(gè)熱愛閱讀的人,而且涉獵廣泛, 喜歡讀小說,雜志。只要一有空爸爸就會(huì)選擇其中的一種來閱讀,并且還會(huì)做筆記。
把上面的情景轉(zhuǎn)化成程序:

class IBook(object):
    def content(self):
        raise NotImplementedError
        
    def read(self):
        print "閱讀"
        
    def note(self):
        print "記筆記"
        
class Novel(IBook):
    def content(self):
        print "這是本小說"
        
class Journal(IBook):
    def content(self):
        print "這是本雜志"
                
class SimpleFactory(object):
    @classmethod
    def choice_book(cls, _type):
        if _type == 'novel':
            return Novel()
        elif _type == 'journal':
            return Journal()
        else:
            raise ValueError, 'unknown book type'
   
if __name__ == '__main__':
    import random
    types = ['novel', 'journal', 'newspaper']
    book = SimpleFactory.choice_book(random.choice(types))
    book.content()
    book.read()
    book.note()

SimpleFactory就是一個(gè)簡單工廠,選擇不同的書籍類進(jìn)行實(shí)例化。

過了一段時(shí)間,因?yàn)榘职值暮冒駱?,兒子和媽媽也開始讀書,但他們和爸爸喜歡的書不一樣,兒子喜歡童話書,科普類書籍,媽媽喜歡看愛情小說,時(shí)尚雜志。

如果還是使用簡單工廠,那么choice_book這個(gè)方法就改成這樣

def choice_book(cls, role, _type):
    if role == 'father':
        if _type == 'novel':
            return FatherNovel()
        elif _type == 'journal':
            return FatherJournal()
        else:
            raise ValueError, '[Father]Unknown book type'
    elif role == 'mother':
        if _type == 'novel':
            return MotherNovel()
        elif _type == 'journal':
            return MotherJournal()
        else:
            raise ValueError, "[Mother]Unknown book type"
    elif role == 'son':
        if _type == 'novel':
            return SonNovel()
        elif _type == 'journal':
            return SonJournal()
        else:
            raise ValueError, "[Son]Unknown book type"
    else:
        raise ValueError, "Unknown role"

可以看到,代碼變得又長又不美觀,而且也不利于擴(kuò)展,比如以后爺爺也要加入家庭閱讀小組,要修改choice_book方法,另外也不符合“開閉原則

為了應(yīng)對(duì)這種情況,就要把簡單工廠升級(jí)為工廠方法。
books.py

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

class IBook(object):  # 產(chǎn)品
    def content(self):
        raise NotImplementedError

    def read(self):
        print "閱讀"

    def note(self):
        print "記筆記"

class FatherNovel(IBook):
    def content(self):
        print "這是本推理小說"

class FatherJournal(IBook):
    def content(self):
        print "這是本財(cái)經(jīng)雜志"

class SonNovel(IBook):
    def content(self):
        print "這是本童話書"

class SonJournal(IBook):
    def content(self):
        print "這是本兒童雜志"

class MotherNovel(IBook):
    def content(self):
        print "這是本愛情小說"

class MotherJournal(IBook):
    def content(self):
        print "這是本時(shí)尚雜志"

readers.py

import books

class IReader(object): # 工廠類
    def read(self, _type):
        book = self.choice_book(_type)
        book.content()
        book.read()
        book.note()

    def choice_book(self, _type):
        raise NotImplementedError

class FatherReader(IReader):
    def choice_book(self, _type):
        if _type == 'novel':
            return books.FatherNovel()
        elif _type == 'journal':
            return books.FatherJournal()
        else:
            raise ValueError, '[Father]Unknown book type'

class MotherReader(IReader):
    def choice_book(self, _type):
        if _type == 'novel':
            return books.MotherNovel()
        elif _type == 'journal':
            return books.MotherJournal()
        else:
            raise ValueError, "[Mother]Unknown book type"

class SonReader(IReader):
    def choice_book(self, _type):
        if _type == 'novel':
            return books.SonNovel()
        elif _type == 'journal':
            return books.SonJournal()
        else:
            raise ValueError, "[Son]Unknown book type"

reading_day.py

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

from readers import FatherReader, MotherReader, SonReader

def main():
    print "家庭閱讀日..."
    father = FatherReader()
    mother = MotherReader()
    son = SonReader()

    print '-' * 5, '爸爸', '-' * 5
    father.read('novel')
    print '-' * 5, '媽媽', '-' * 5
    mother.read('novel')
    print '-' * 5, '兒子', '-' * 5
    son.read('novel')

if __name__ == '__main__':
    main()

輸出

家庭閱讀日...
----- 爸爸 -----
這是本推理小說
閱讀
記筆記
----- 媽媽 -----
這是本愛情小說
閱讀
記筆記
----- 兒子 -----
這是本童話書
閱讀
記筆記

現(xiàn)在爺爺也要加入家庭閱讀小組,不需要改動(dòng)已有的代碼,只要在books.py模塊加上爺爺要讀的書

class GrandpaNovel(IBook):
    def content(self):
        print "這是本歷史小說"

class GrandpaJournal(IBook):
    def content(self):
        print "這是本老年雜志"

增加一個(gè)GrandpaReader子類

class GrandReader(IReader):
    def choice_book(self, _type):
        if _type == 'novel':
            return books.GrandpaNovel()
        elif _type == 'journal':
            return books.GrandpaJournal()
        else:
            raise ValueError, “[Grandpa]Unknown book type"

最后在閱讀日里加入爺爺就可以

grandpa = GrandpaReader()
grandpa.read(‘novel’)

可以看到完全沒有改動(dòng)到爸爸,媽媽和兒子的代碼,增加爺爺之后,也只要對(duì)爺爺進(jìn)行單元測(cè)試即可,符合“開閉原則”

現(xiàn)在我們可以對(duì)工廠方法下個(gè)定義了

工廠方法定義了一個(gè)創(chuàng)建對(duì)象的接口,但由子類決定要實(shí)例化的類是哪個(gè)。工廠方法讓類把實(shí)例化推遲到之類。

OK,隨著家庭閱讀日的堅(jiān)持,家庭成員們的閱讀能力逐漸提升,但是對(duì)于不同書籍,每個(gè)家庭成員的閱讀方式和記筆記的方式都有區(qū)別,比如爸爸是個(gè)狂熱的推理迷,對(duì)于推理小說喜歡精讀,并且記錄每個(gè)人物關(guān)系,但是對(duì)于財(cái)經(jīng)雜志則只是速讀,也不會(huì)記筆記;而媽媽和兒子,不管是小說或時(shí)尚雜志,都只是速讀,也不會(huì)記筆記。

總結(jié)起來就是說不同的書有不同的閱讀和筆記方法,那么代碼上改如何實(shí)現(xiàn)呢?
因?yàn)閞ead()和note()都是IBook的方法,而所有的書籍類都是繼承IBook,那么我們直接在每個(gè)書籍類里重寫這兩個(gè)方法就可以實(shí)現(xiàn)功能了。這當(dāng)然可以實(shí)現(xiàn)功能,但這樣一來會(huì)導(dǎo)致出現(xiàn)大量重復(fù)代碼,不可??!
那么分別把閱讀和筆記作為產(chǎn)品,并創(chuàng)建工廠類,將工廠類實(shí)例傳遞給書籍類,讓不同的書籍類選擇實(shí)例化不同的閱讀或者筆記的類。這樣一來減少了重復(fù)代碼,而且也方便增加新的閱讀和筆記方法,提高了可擴(kuò)展性??瓷先ズ芡昝?,但是還是存在一個(gè)問題,還是要書籍類還是要重寫IBook的read()和note()方法,另外如果要增加一個(gè)新的動(dòng)作呢?比如讀后處理書籍,保留或者二手轉(zhuǎn)賣。要修改大量的代碼,而且也不符合“開閉原則”。

對(duì)書籍而言,閱讀和筆記這兩個(gè)產(chǎn)品是有關(guān)聯(lián)性的。應(yīng)對(duì)這種有關(guān)聯(lián)性的產(chǎn)品的場(chǎng)景,抽象工廠就非常合適。來看它的定義

抽象工廠提供一個(gè)接口,用于創(chuàng)建相關(guān)或依賴對(duì)象的家族,而不需要明確指定具體的類。

首先要把閱讀和記筆記兩個(gè)動(dòng)作抽象出來,做成接口

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

# 閱讀
class IReadAction(object):
    def read(self):
        raise NotImplementedError

class SpeedReadAction(IReadAction):
    def read(self):
        print "速讀"

class IntensiveReadAction(IReadAction):
    def read(self):
        print “精讀"

# 筆記
class INoteAction(object):
    def note(self):
        raise NotImplementedError

class FullNoteAction(INoteAction):
    def note(self):
        print "詳細(xì)筆記"

class NoneNoteAction(INoteAction):
    def note(self):
        print "不做筆記"

接著為閱讀和記筆記兩個(gè)動(dòng)作創(chuàng)建工廠

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

from read_action import SpeedReadAction, IntensiveReadAction
from note_action import FullNoteAction, NoneNoteAction

class ActionFactory(object):

    def read_action(self):
        raise NotImplementedError

    def note_action(self):
        raise NotImplementedError

class FatherNovelActionFactory(ActionFactory):
    def read_action(self):
        IntensiveReadAction().read()

    def note_action(self):
        FullNoteAction().note()

class FatherJournalActionFactory(ActionFactory):
    def read_action(self):
        SpeedReadAction().read()

    def note_action(self):
        NoneNoteAction().note()

class MotherAndSonActionFactory(ActionFactory):
    def read_action(self):
        SpeedReadAction().read()
    
    def note_action(self):
        NoneNoteAction().note()

然后修改books.py模塊,具體的Book子類不用改,只要改IBook接口就可以

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

class IBook(object):

    def __init__(self, action_factory):
        self.action_factory = action_factory

    def content(self):
        raise NotImplementedError

    def read(self):
        self.action_factory.read_action()

    def note(self):
        self.action_factory.note_action()

class FatherNovel(IBook):
    def content(self):
        print "這是本推理小說"

class FatherJournal(IBook):
    def content(self):
        print "這是本財(cái)經(jīng)雜志"

class SonNovel(IBook):
    def content(self):
        print "這是本童話書"

class SonJournal(IBook):
    def content(self):
        print "這是本兒童雜志"

class MotherNovel(IBook):
    def content(self):
        print "這是本愛情小說"

class MotherJournal(IBook):
    def content(self):
        print "這是本時(shí)尚雜志"

最后修改readers.py模塊,為每種book加上不同的ActionFactory即可

import books
from action import FatherJournalActionFactory, FatherNovelActionFactory, MotherAndSonActionFactory

class IReader(object):
    def read(self, _type):
        book = self.choice_book(_type)
        book.content()
        book.read()
        book.note()

    def choice_book(self, _type):
        raise NotImplementedError

class FatherReader(IReader):
    def choice_book(self, _type):
        if _type == 'novel':
            return books.FatherNovel(FatherNovelActionFactory()) # 改變部分
        elif _type == 'journal':
            return books.FatherJournal(FatherJournalActionFactory()) # 改變部分
        else:
            raise ValueError, '[Father]Unknown book type'


class MotherReader(IReader):
    def choice_book(self, _type):
        if _type == 'novel':
            return books.MotherNovel(MotherAndSonActionFactory()) # 改變部分
        elif _type == 'journal':
            return books.MotherJournal(MotherAndSonActionFactory()) # 改變部分
        else:
            raise ValueError, "[Mother]Unknown book type"

class SonReader(IReader):
    def choice_book(self, _type):
        if _type == 'novel':
            return books.SonNovel(MotherAndSonActionFactory()) # 改變部分
        elif _type == 'journal':
            return books.SonJournal(MotherAndSonActionFactory()) # 改變部分
        else:
            raise ValueError, "[Son]Unknown book type"

到此為止我們就完成了,reading_day模塊完全不需要在改動(dòng),但為了測(cè)試爸爸對(duì)推理小說和財(cái)政雜志的不同閱讀和筆記動(dòng)作,我們讓爸爸同時(shí)讀推理小說和財(cái)經(jīng)雜志。

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


from readers import FatherReader, MotherReader, SonReader

def main():
    print "家庭閱讀日..."
    father = FatherReader()
    mother = MotherReader()
    son = SonReader()

    print '-' * 5, '爸爸', '-' * 5
    father.read('novel')
    print '-' * 5
    father.read('journal')
    print '-' * 5, '媽媽', '-' * 5
    mother.read('novel')
    print '-' * 5, '兒子', '-' * 5
    son.read('novel')

if __name__ == '__main__':
    main()

輸出

----- 爸爸 -----
這是本推理小說
精讀
詳細(xì)筆記
-----
這是本財(cái)經(jīng)雜志
速讀
不做筆記
----- 媽媽 -----
這是本愛情小說
速讀
不做筆記
----- 兒子 -----
這是本童話書
速讀
不做筆記

可以看到引入抽象工廠之后,完美的解決了問題,既不需要讓書籍類重寫父類方法,另外如果要增加處理書籍的動(dòng)作,也只需要?jiǎng)?chuàng)建處理產(chǎn)品,并加入到ActionFactory工廠中即可,非常方便而且符合“開閉原則”。

總結(jié)

說了怎么多,那么使用工廠模式到底有什么好處呢?我的理解:
首先,工廠模式可以解耦,把實(shí)例的創(chuàng)建和使用過程分開。比如Class A要調(diào)用Class B,那么Class A只是調(diào)用Class B的方法,而不用去實(shí)例化Class B,實(shí)例化的動(dòng)作交給工廠類。
其次,工廠模式將類實(shí)例化過程統(tǒng)一管理起來了,方便以后維護(hù)。
比如Class B要改類名了,而它在很多類里都有被實(shí)例化,你就要一個(gè)個(gè)去改,如果使用工廠模式,你只要在工廠類里改就可以了。
再比如,Class B 要替換成Class C,也只要在工廠類里修改即可。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 標(biāo)簽: python 設(shè)計(jì)模式 工廠模式 引子 如何將實(shí)例化具體類的代碼從應(yīng)用中抽離,或者封裝起來,使它們不會(huì)干擾...
    plectrum閱讀 1,238評(píng)論 2 7
  • 工廠模式,一個(gè)工廠實(shí)例化一個(gè)指定類。
    蝦想家閱讀 323評(píng)論 0 0
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,535評(píng)論 19 139
  • 我是很不喜歡小貓小狗這類看起來毛茸茸的生物的。 但是我覺得如果連一個(gè)喜歡的小動(dòng)物都沒有,會(huì)給人很冷漠的感覺。 所以...
    魚不敢當(dāng)閱讀 176評(píng)論 0 0
  • 忙著工作,忙著生活,已經(jīng)很少有時(shí)間靜下來看書,996的工作,忙里偷閑花幾個(gè)晚上看完了阿西莫夫的《永恒的終結(jié)》,鋪墊...
    玄貓大人閱讀 483評(píng)論 0 0

友情鏈接更多精彩內(nèi)容