介紹
工廠模式是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,也只要在工廠類里修改即可。