這篇文章主要為大家展示了“Python中工廠方法模式有什么用”,內(nèi)容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領(lǐng)大家一起研究并學(xué)習(xí)一下“Python中工廠方法模式有什么用”這篇文章吧。
讓客戶滿意是我們工作的目標(biāo),不斷超越客戶的期望值來自于我們對這個行業(yè)的熱愛。我們立志把好的技術(shù)通過有效、簡單的方式提供給客戶,將通過不懈努力成為客戶在信息化領(lǐng)域值得信任、有價值的長期合作伙伴,公司提供的服務(wù)項目有:主機(jī)域名、網(wǎng)頁空間、營銷軟件、網(wǎng)站建設(shè)、海拉爾網(wǎng)站維護(hù)、網(wǎng)站推廣。
工廠方法(Factory Method)模式又稱為虛擬構(gòu)造器(Virtual Constructor)模式或者多態(tài)工廠(Polymorphic Factory)模式,屬于類的創(chuàng)建型模式。在工廠方法模式中,父類負(fù)責(zé)定義創(chuàng)建對象的公共接口,而子類則負(fù)責(zé)生成具體的對象,這樣做的目的是將類的實例化操作延遲到子類中完成,即由子類來決定究竟應(yīng)該實體化哪一個類。
在簡單工廠模式中,一個工廠類處于對產(chǎn)品類進(jìn)行實例化的中心位置上,它知道每一個產(chǎn)品類的細(xì)節(jié),并決定何時哪一個產(chǎn)品類應(yīng)當(dāng)被實例化。簡單工廠模式的優(yōu)點是能夠使客戶端獨立于產(chǎn)品的創(chuàng)建過程,并且在系統(tǒng)中引入新產(chǎn)品時無需對客戶端進(jìn)行修改,缺點是當(dāng)有新產(chǎn)品要加入到系統(tǒng)中時,必須對工廠類進(jìn)行修改,以加入必要的處理邏輯。簡單工廠模式的致命弱點就是處于核心地位的工廠類,因為一旦它無法確定要對哪個類進(jìn)行實例化時,就無法使用該模式,而工廠方法模式則可以很好地避免這一問題。
考慮這樣一個應(yīng)用程序框架(Framework),它可以用來瀏覽各種格式的文檔,如TXT、DOC、PDF、HTML等,設(shè)計時為了讓軟件的體系結(jié)構(gòu)能夠盡可能地通用,定義了Application和Document這兩個抽象父類,客戶必須通過它們的子類來處理某一具體類型的文檔。例如,要想利用該框架來編寫一個PDF文件瀏覽器,必須先定義PDFApplication和PDFDocument這兩個類,它們應(yīng)該分別繼承于Application和Document。
Application的職責(zé)是對Document進(jìn)行管理,并且在需要時創(chuàng)建它們,比如當(dāng)用戶從菜單中選擇Open或者New的時候,Application就要負(fù)責(zé)創(chuàng)建一個Document的實例。顯而易見,被實例化的特定Document子類是與具體應(yīng)用相關(guān)的,因此Application無法預(yù)測哪個Document的子類將被實例化,它只知道一個新的Document何時(When)被創(chuàng)建,但并不知道哪種(Which)具體的Document將被創(chuàng)建。此時若仍堅持使用簡單工廠模式會出現(xiàn)一個非常尷尬的局面:框架必須實例化類,但它只知道不能被實例化的抽象類。
解決的辦法是使用工廠方法模式,它封裝了哪一個Document子類將被創(chuàng)建的信息,并且能夠?qū)⑦@些信息從框架中分離出來。如圖1所示,Application的子類重新定義了Application的抽象方法createDocument(),并返回某個恰當(dāng)?shù)腄ocument子類的實例。我們稱createDocument()是一個工廠方法(factory method),因為它非常形象地描述了類的實例化過程,即負(fù)責(zé)"生產(chǎn)"一個對象。
簡單說來,工廠方法模式的作用就是可以根據(jù)不同的條件生成各種類的實例,這些實例通常屬于多個相似的類型,并且具有共同的父類。工廠方法模式將這些實例的創(chuàng)建過程封裝了起來,從而簡化了客戶程序的編寫,并改善了軟件體系結(jié)構(gòu)的可擴(kuò)展性,使得將來能夠以最小的代價加入新的子類。工廠方法這一模式適合在如下場合中運(yùn)用:
◆當(dāng)無法得知必須創(chuàng)建的對象屬于哪個類的時候,或者無法得知屬于哪個類的對象將被返回的時候,但前提是這些對象都符合一定的接口標(biāo)準(zhǔn)。
◆當(dāng)一個類希望由它的子類來決定所創(chuàng)建的對象的時候,其目的是使程序的可擴(kuò)展性更好,在加入其他類時更具彈性。
◆當(dāng)創(chuàng)建對象的職責(zé)被委托給多個幫助子類(helper subclass)中的某一個,并且希望將哪個子類是代理者這一信息局部化的時候。
需要說明的是,使用工廠方法模式創(chuàng)建對象并不意味著一定會讓代碼變得更短(實事上往往更長),并且可能需要設(shè)計更多的輔助類,但它的確可以靈活地、有彈性地創(chuàng)建尚未確定的對象,從而簡化了客戶端應(yīng)用程序的邏輯結(jié)構(gòu),并提高了代碼的可讀性和可重用性。
二、模式引入
工廠方法這一模式本身雖然并不復(fù)雜,但卻是最重要的設(shè)計模式之一,無論是在COM、CORBA或是EJB中,都可以隨處見到它的身影。面向?qū)ο蟮囊粋€基本思想是在不同的對象間進(jìn)行責(zé)權(quán)的合理分配,從本質(zhì)上講,工廠方法模式是一種用來創(chuàng)建對象的多態(tài)方法(polymorphic method),它在抽象父類中聲明用來創(chuàng)建對象的方法接口,而具體子類則通過覆蓋該方法將對象的創(chuàng)建過程局部化,包括是否實例化一個子類,以及是否對它進(jìn)行初始化等等。從某種程度上說,工廠方法可以看成是構(gòu)造函數(shù)的特殊化,其特殊性表現(xiàn)在能夠用一致的方法來創(chuàng)建不同的對象,而不用擔(dān)心當(dāng)前正在對哪個類進(jìn)行實例化,因為究竟創(chuàng)建哪個類的對象將取決于它的子類。
假設(shè)我們打算開發(fā)一個用于個人信息管理(Personal Information Manager,PIM)的軟件,它可以保存日常工作和生活中所需的各種信息,包括地址本、電話簿、約會提醒、日程安排等等。很顯然,PIM用戶界面(User Interface)的設(shè)計將是比較復(fù)雜的,因為必須為每種信息的輸入、驗證和修改都提供單獨的界面,以便同用戶進(jìn)行交互。比較簡單的做法是在PIM中為各種信息的處理編寫相應(yīng)的用戶界面,但代價是將導(dǎo)致軟件的可擴(kuò)展性非常差,因為一旦今后要加入對其他信息(比如銀行帳戶)進(jìn)行管理的功能時,就必須對PIM進(jìn)行修改,添加相應(yīng)的用戶界面,從而最終導(dǎo)致PIM變得越來越復(fù)雜,結(jié)構(gòu)龐大而難以維護(hù)。改進(jìn)的辦法是將處理各種信息的用戶界面從PIM中分離出來,使PIM不再關(guān)心用戶如何輸入數(shù)據(jù),如何對用戶輸入進(jìn)行驗證,以及用戶如何修改信息等,所有的這些都交由一個專門的軟件模塊來完成,而PIM要做的只是提供一個對這些個人信息進(jìn)行管理的總體框架。
在具體實現(xiàn)時可以設(shè)計一個通用接口Editable,并且讓所有處理特定個人信息(如通信地址和電話號碼)的用戶界面都繼承于它,而PIM則通過Editable提供的方法getEditor()獲得Editor的一個實例,并利用它來對用戶輸入進(jìn)行統(tǒng)一的處理。例如,當(dāng)用戶完成輸入之后,PIM可以調(diào)用Editor中的方法getContent()來獲取用戶輸入的數(shù)據(jù),或者調(diào)用resetUI()來清除用戶輸入的數(shù)據(jù)。在采用這一體系結(jié)構(gòu)之后,如果要擴(kuò)展PIM的功能,只需添加與之對應(yīng)的Editable和Editor就可以了,而不用對PIM本身進(jìn)行修改。
現(xiàn)在離目標(biāo)還有一步之遙,由于Editable和Editor都只是通用的接口,但PIM卻需要對它們的子類進(jìn)行實例化,此時自然應(yīng)該想到運(yùn)用工廠方法模式,為PIM定義一個EditableFactory接口來創(chuàng)建Editable的對象。這樣一來,整個PIM的體系結(jié)構(gòu)就將如圖2所示。
Editable接口定義了一個公共的構(gòu)造性方法(builder method)getEditor(),它返回一個Editor對象,其完整的代碼如清單1所示。任何一項個人信息都擁有自己獨立的用戶界面(Editor),負(fù)責(zé)獲取數(shù)據(jù)并在需要的時候進(jìn)行修改,而PIM唯一要做事情的只是通過Editable來獲得Editor,并利用它來對用戶輸入的數(shù)據(jù)進(jìn)行相應(yīng)的操作。
代碼清單1:editable.py class Editable: """ 個人信息用戶界面的公共接口 """ # 獲得個人信息編輯界面 def getEditor(self): pass
Editor接口給出了處理所有個人信息的公共接口,其完整的代碼如清單2所示。PIM通過調(diào)用getUI()方法能夠獲得與用戶進(jìn)行交互的UI組件,根據(jù)當(dāng)前正在處理的個人信息的不同,這些組件可能簡單到只是一個文本輸入框,也可以復(fù)雜到是一個包含了多個圖形控件(Widget)的對話框。利用Editor提供的getContent()、commitChanges()和resetUI()方法,PIM還可以獲取、提交或者清空用戶輸入的個人信息。在引入Editor之后, PIM就能夠從處理特定個人信息的用戶界面中解脫出來,從而可以將注意力集中在如何對這些信息進(jìn)行統(tǒng)一管理的問題上。
代碼清單2:editor.py class Editor: """ 用戶使用特定的Editor來編輯個人信息 """ # 獲取代表用戶界面(UI)的對象 def getUI(self): pass # 獲取用戶輸入的數(shù)據(jù) def getContent(self): pass # 提交用戶輸入的數(shù)據(jù) def commitChanges(self): pass # 清空用戶輸入的數(shù)據(jù) def resetUI(self): pass
EditableAddress是Editable的一個具體實現(xiàn),PIM使用它來處理個人地址信息,其完整的代碼如清單3所示。
代碼清單3:editableaddress.py from editor import Editor from editable import Editable import Tkinter class EditableAddress(Editable): """ 用于處理個人地址信息的Editable """ # 構(gòu)造函數(shù) def __init__(self, master): self.master = master self.name = "" self.province = "" self.city = "" self.street = "" self.zipcode = "" self.editor = AddressEditor(self) # 獲取相關(guān)聯(lián)的Editor def getEditor(self): return self.editor class AddressEditor(Editor, Tkinter.Frame): """ 用于處理個人地址信息的Editor """ # 構(gòu)造函數(shù) def __init__(self, owner): Tkinter.Frame.__init__(self, owner.master) self.owner = owner self.name = Tkinter.StringVar() self.province = Tkinter.StringVar() self.city = Tkinter.StringVar() self.street = Tkinter.StringVar() self.zipcode = Tkinter.StringVar() self.createWidgets() # 構(gòu)造用戶界面 def createWidgets(self): # 姓名 nameFrame = Tkinter.Frame(self) nameLabel = Tkinter.Label(nameFrame, text="Name:") nameEntry = Tkinter.Entry(nameFrame, textvariable=self.name) nameLabel.config(anchor=Tkinter.E, width=8, pady=3) nameLabel.pack(side=Tkinter.LEFT) nameEntry.pack(side=Tkinter.LEFT) nameFrame.pack() # 省份 provinceFrame = Tkinter.Frame(self) provinceLabel = Tkinter.Label(provinceFrame, text="Province:") provinceEntry = Tkinter.Entry(provinceFrame, textvariable=self.province) provinceLabel.config(anchor=Tkinter.E, width=8, pady=3) provinceLabel.pack(side=Tkinter.LEFT) provinceEntry.pack(side=Tkinter.LEFT) provinceFrame.pack() # 城市 cityFrame = Tkinter.Frame(self) cityLabel = Tkinter.Label(cityFrame, text="City:") cityEntry = Tkinter.Entry(cityFrame, textvariable=self.city) cityLabel.config(anchor=Tkinter.E, width=8, pady=3) cityLabel.pack(side=Tkinter.LEFT) cityEntry.pack(side=Tkinter.LEFT) cityFrame.pack() # 街道 streetFrame = Tkinter.Frame(self) streetLabel = Tkinter.Label(streetFrame, text="Street:") streetEntry = Tkinter.Entry(streetFrame, textvariable=self.street) streetLabel.config(anchor=Tkinter.E, width=8, pady=3) streetLabel.pack(side=Tkinter.LEFT) streetEntry.pack(side=Tkinter.LEFT) streetFrame.pack() # 郵編 zipcodeFrame = Tkinter.Frame(self) zipcodeLabel = Tkinter.Label(zipcodeFrame, text="ZIP Code:") zipcodeEntry = Tkinter.Entry(zipcodeFrame, textvariable=self.zipcode) zipcodeLabel.config(anchor=Tkinter.E, width=8, pady=3) zipcodeLabel.pack(side=Tkinter.LEFT) zipcodeEntry.pack(side=Tkinter.LEFT) zipcodeFrame.pack() # 重載Editor中的方法,獲取代表用戶界面(UI)的對象 def getUI(self): return self # 重載Editor中的方法,獲取用戶輸入的數(shù)據(jù) def getContent(self): content = " Name: " + self.name.get() + "\n" content += "Province: " + self.province.get() + "\n" content += " City: " + self.city.get() + "\n" content += " Street: " + self.street.get() + "\n" content += "ZIP Code: " + self.zipcode.get() return content # 重載Editor中的方法,提交用戶輸入的數(shù)據(jù) def commitChanges(self): selfself.owner.name = self.name.get() selfself.owner.province = self.province.get() selfself.owner.city = self.city.get() selfself.owner.street = self.street.get() selfself.owner.zipcode = self.zipcode.get() # 重載Editor中的方法,清空用戶輸入的數(shù)據(jù) def resetUI(self): self.name.set("") self.province.set("") self.city.set("") self.street.set("") self.zipcode.set("")
EditablePhone是Editable的另一個具體實現(xiàn),PIM使用它來處理個人電話號碼,其完整的代碼如清單4所示。
代碼清單4:editablephone.py from editor import Editor from editable import Editable import Tkinter class EditablePhone(Editable): """ 用于處理個人電話號碼的Editable """ # 構(gòu)造函數(shù) def __init__(self, master): self.master =master self.areaCode = ""; self.phoneNumber = "" self.editor = PhoneEditor(self) # 獲取相關(guān)聯(lián)的Editor def getEditor(self): return self.editor class PhoneEditor(Editor, Tkinter.Frame): """ 用于處理個人電話號碼的Editor """ # 構(gòu)造函數(shù) def __init__(self, owner): self.owner = owner Tkinter.Frame.__init__(self, self.owner.master) self.areaCode = Tkinter.StringVar() self.phoneNumber = Tkinter.StringVar() # 構(gòu)造用戶界面 codeLabel = Tkinter.Label(self, text="Area Code:") codeEntry = Tkinter.Entry(self, textvariable=self.areaCode) codeLabel.config(anchor=Tkinter.E, width=12, pady=3) codeLabel.grid(row=0, column=0) codeEntry.grid(row=0, column=1) numberLabel = Tkinter.Label(self, text="Phone Number:") numberEntry = Tkinter.Entry(self, textvariable=self.phoneNumber) numberLabel.config(anchor=Tkinter.E, width=12, pady=3) numberLabel.grid(row=1, column=0) numberEntry.grid(row=1, column=1) # 重載Editor中的方法,獲取代表用戶界面(UI)的對象 def getUI(self): return self # 重載Editor中的方法,獲取用戶輸入的數(shù)據(jù) def getContent(self): content = " Area Code: " + self.areaCode.get() + "\n" content += "Phone Number: " + self.phoneNumber.get() + "\n" return content # 重載Editor中的方法,提交用戶輸入的數(shù)據(jù) def commitChanges(self): selfself.owner.areaCode = self.areaCode.get() selfself.owner.phoneNumber = self.phoneNumber.get() # 重載Editor中的方法,清空用戶輸入的數(shù)據(jù) def resetUI(self): self.areaCode.set("") self.phoneNumber.set("")
EditableFactory接口是在PIM中應(yīng)用工廠方法模式的核心,其完整的代碼如清單5所示。與簡單工廠模式中負(fù)責(zé)創(chuàng)建所有對象的"超級類"不同,EditableFactory只定義了如何實例化Editable的工廠方法createEditable(),并不掌握它們的生殺大權(quán),真正負(fù)責(zé)完成創(chuàng)建工作的是EditableFactory的子類。
代碼清單5:editablefactory.py class EditableFactory: """ 用于創(chuàng)建Editable的工廠類 """ # 實例化Editable對象 def createEditable(self, master): pass
EditableAddressFactory是EditableFactory的一個具體實現(xiàn),PIM使用它來實例化EditableAddress對象,其完整的代碼如清單6所示。
代碼清單6:editableaddressfactory.py from editablefactory import EditableFactory from editableaddress import EditableAddress class EditableAddressFactory(EditableFactory): """ 用于創(chuàng)建EditableAddress的工廠類 """ # 重載EditableFactory中的方法,實例化EditableAddress對象 def createEditable(self, master): address = EditableAddress(master) return address
EditablePhoneFactory是EditableFactory的另一個具體實現(xiàn),PIM使用它來實例化EditablePhone對象,其完整的代碼如清單7所示。
代碼清單7:editablephonefactory.py from editablefactory import EditableFactory from editablephone import EditablePhone class EditablePhoneFactory(EditableFactory): """ 用于創(chuàng)建EditablePhone的工廠類 """ # 重載EditableFactory中的方法,實例化EditablePhone對象 def createEditable(self, master): phone = EditablePhone(master) return phone
所有這些輔助類都定義好之后,接下去就可以編寫PIM類了,它提供了一個對各種個人信息進(jìn)行統(tǒng)一管理的框架,其完整的代碼如清單8所示。
代碼清單8:pim.py from editablephone import EditablePhone from editableaddressfactory import EditableAddressFactory from editablephonefactory import EditablePhoneFactory import Tkinter class PIM: """ 個人信息管理 """ # 構(gòu)造函數(shù) def __init__(self): mainFrame = Tkinter.Frame() mainFrame.master.title("PIM") # 命令按鈕 addressButton = Tkinter.Button(mainFrame, width=10, text="Address") phoneButton = Tkinter.Button(mainFrame, width=10, text="Phone") commitButton = Tkinter.Button(mainFrame, width=10, text="Commit") resetButton = Tkinter.Button(mainFrame, width=10, text="Reset") addressButton.config(command=self.addressClicked) phoneButton.config(command=self.phoneClicked) commitButton.config(command=self.commitClicked) resetButton.config(command=self.resetClicked) addressButton.grid(row=0, column=1, padx=10, pady=5, stick=Tkinter.E) phoneButton.grid(row=1, column=1, padx=10, pady=5, stick=Tkinter.E) commitButton.grid(row=2, column=1, padx=10, pady=5, stick=Tkinter.E) resetButton.grid(row=3, column=1, padx=10, pady=5, stick=Tkinter.E) # 用來容納各類Editor的容器 self.editorFrame = Tkinter.Frame(mainFrame) self.editorFrame.grid(row=0, column=0, rowspan=4) self.editorFrame.grid_configure(stick=Tkinter.N, pady=15) self.editor = Tkinter.Frame(self.editorFrame) self.editor.grid() # 個人信息顯示區(qū)域 self.content = Tkinter.StringVar() self.contentLabel = Tkinter.Label(mainFrame, width=50, height=5) self.contentLabel.configure(textvariable=self.content) self.contentLabel.configure(anchor=Tkinter.W, font="Arial 10 italic bold") self.contentLabel.configure(relief=Tkinter.RIDGE, pady=5, padx=10) self.contentLabel.grid(row=4, column=0, columnspan=2) mainFrame.pack() mainFrame.mainloop() # Address按鈕的回調(diào)函數(shù) def addressClicked(self): address = EditableAddressFactory().createEditable(self.editorFrame) self.editor.grid_remove() self.editor = address.getEditor() self.editor.getUI().grid() # Phone按鈕的回調(diào)函數(shù) def phoneClicked(self): phone = EditablePhoneFactory().createEditable(self.editorFrame) self.editor.grid_remove() self.editor = phone.getEditor() self.editor.getUI().grid() # Commit按鈕的回調(diào)函數(shù) def commitClicked(self): content = self.editor.getContent() self.content.set(content) # Reset按鈕的回調(diào)函數(shù) def resetClicked(self): self.editor.resetUI() # 主函數(shù) if (__name__ == "__main__"): app = PIM()
圖3是PIM在運(yùn)行時的界面效果。
三、一般結(jié)構(gòu)
工廠方法模式是簡單工廠模式的進(jìn)一步抽象和推廣,它不僅保持了簡單工廠模式能夠向客戶隱藏類的實例化過程這一優(yōu)點,而且還通過多態(tài)性克服了工廠類過于復(fù)雜且不易于擴(kuò)展的缺點。在工廠方法模式中,處于核心地位的工廠類不再負(fù)責(zé)所有產(chǎn)品的創(chuàng)建,而是將具體的創(chuàng)建工作交由子類去完成。工廠方法模式中的核心工廠類經(jīng)過功能抽象之后,成為了一個抽象的工廠角色,僅負(fù)責(zé)給出具體工廠子類必須實現(xiàn)的接口,而不涉及哪種產(chǎn)品類應(yīng)當(dāng)被實例化這一細(xì)節(jié)。工廠方法模式的一般性結(jié)構(gòu)如圖4所示,圖中為了簡化只給出了一個產(chǎn)品類和一個工廠類,但在實際系統(tǒng)中通常需要設(shè)計多個產(chǎn)品類和多個工廠類。
工廠方法模式的實質(zhì)是將對象的創(chuàng)建延遲到其子類實現(xiàn),即由子類根據(jù)當(dāng)前情況動態(tài)決定應(yīng)該實例化哪一個產(chǎn)品類。從上圖可以看出,工廠方法模式涉及到抽象工廠角色、具體工廠角色、抽象產(chǎn)品角色和具體產(chǎn)品角色四個參與者。
◆抽象工廠(Creator)角色:是工廠方法模式的核心,它負(fù)責(zé)定義創(chuàng)建抽象產(chǎn)品對象的工廠方法。抽象工廠不能被外界直接調(diào)用,但任何在模式中用于創(chuàng)建產(chǎn)品對象的工廠類都必須實現(xiàn)由它所定義的工廠方法。
具體工廠(Concrete Creator)角色:是工廠方法模式的對外接口,它負(fù)責(zé)實現(xiàn)創(chuàng)建具體產(chǎn)品對象的內(nèi)部邏輯。具體工廠與應(yīng)用密切相關(guān),可以被外界直接調(diào)用,創(chuàng)建所需要的產(chǎn)品。
抽象產(chǎn)品(Product)角色:是工廠方法模式所創(chuàng)建的所有對象的父類,它負(fù)責(zé)描述所有具體產(chǎn)品共有的公共接口。
具體產(chǎn)品(Concrete Product)角色:是工廠方法模式的創(chuàng)建目標(biāo),所有創(chuàng)建的對象都是充當(dāng)這一角色的某個具體類的實例。
抽象工廠角色負(fù)責(zé)聲明工廠方法(factory method),用來"生產(chǎn)"抽象產(chǎn)品,以下是抽象工廠的示例性Python代碼:
代碼清單9:creator.py class Creator: """ 抽象工廠角色 """ # 創(chuàng)建抽象產(chǎn)品的工廠方法 def factoryMethod(self): pass
具體工廠角色負(fù)責(zé)創(chuàng)建一個具體產(chǎn)品的實例,并將其返回給調(diào)用者。具體工廠是與具體產(chǎn)品相關(guān)的,實現(xiàn)時一般常用的做法是為每個具體產(chǎn)品定義一個具體工廠。以下是具體工廠的示例性Python代碼:
代碼清單10:concretecreator.py class ConcreteCreator(Creator): """ 具體工廠角色 """ # 創(chuàng)建具體產(chǎn)品的工廠方法 def factoryMethod(self): product = ConcreteProduct() return product
抽象產(chǎn)品角色的主要目的是為所有的具體產(chǎn)品提供一個共同的接口,通常只需給出相應(yīng)的聲明就可以了,而不用給出具體的實現(xiàn)。以下是抽象產(chǎn)品類的示例性Python代碼:
代碼清單11:product.py class Product: """ 抽象產(chǎn)品角色 """ # 所有產(chǎn)品類的公共接口 def interface(self): pass
具體產(chǎn)品角色充當(dāng)最終的創(chuàng)建目標(biāo),一般來講它是抽象產(chǎn)品類的子類,實現(xiàn)了抽象產(chǎn)品類中定義的所有工廠方法,實際應(yīng)用時通常會具有比較復(fù)雜的業(yè)務(wù)邏輯。以下是具體產(chǎn)品類的示例性Python代碼:
代碼清單12:concreteproduct.py class ConcreteProduct(Product): """ 具體產(chǎn)品角色 """ # 公共接口的實現(xiàn) def interface(self): print "Concrete Product Method"
在應(yīng)用工廠方法模式時,通常還需要再引入一個客戶端角色,由它負(fù)責(zé)創(chuàng)建具體的工廠對象,然后再調(diào)用工廠對象中的工廠方法來創(chuàng)建相應(yīng)的產(chǎn)品對象。以下是客戶端的示例性Python代碼:
代碼清單13:client.py class Client: """ 客戶端角色 """ def run(self): creator = ConcreteCreator() product = creator.factoryMethod() product.interface() # 主函數(shù) if (__name__ == "__main__"): client = Client() client.run()
在這個簡單的示意性實現(xiàn)里,充當(dāng)具體產(chǎn)品和具體工廠角色的類都只有一個,但在真正的實際應(yīng)用中,通常遇到的都是同時會有多個具體產(chǎn)品類的情況,此時相應(yīng)地需要提供多個具體工廠類,每個具體工廠都負(fù)責(zé)生產(chǎn)對應(yīng)的具體產(chǎn)品。
工廠方法模式的活動序列如圖5所示,客戶端Client首先創(chuàng)建ConcreteCreator對象,然后調(diào)用ConcreteCreator對象的工廠方法factoryMethod(),由它負(fù)責(zé)"生產(chǎn)"出所需要的ConcreteProduct對象。
四、實際運(yùn)用
使用工廠方法模式可以在不修改具體工廠角色的情況下引入新的產(chǎn)品,這一點無疑使得工廠方法模式具有比簡單工廠模式更好的可擴(kuò)展性。在開發(fā)實際的軟件系統(tǒng)時,通常是先設(shè)計產(chǎn)品角色,然后才開始設(shè)計工廠角色,而復(fù)雜的需求導(dǎo)致將在抽象產(chǎn)品和具體產(chǎn)品之間形成非常龐大的樹狀結(jié)構(gòu),如圖6所示。
在上面的產(chǎn)品等級結(jié)構(gòu)中,出現(xiàn)了多于一個的抽象產(chǎn)品類,以及多于兩個的類層次,這是在構(gòu)造真實系統(tǒng)中經(jīng)常遇到的情況。在為這一軟件體系結(jié)構(gòu)應(yīng)用工廠方法模式時,通常的做法是按照產(chǎn)品的等級結(jié)構(gòu)再設(shè)計一個相同的工廠等級結(jié)構(gòu),如圖7所示。
定義工廠角色的目的是為了創(chuàng)建相應(yīng)的產(chǎn)品角色,因此整個系統(tǒng)的架構(gòu)將如圖8所示。這一結(jié)構(gòu)常常被稱為平行的類層次(parallel class hierarchies),它使得一個類能夠?qū)⑺囊恍┞氊?zé)委托給另一個獨立的類,而工廠方法則是聯(lián)系兩者之間的紐帶。工廠方法模式并沒有限制產(chǎn)品等級的層數(shù),雖然前面給出的一般性結(jié)構(gòu)中只有兩個層次(抽象產(chǎn)品層和具體產(chǎn)品層),但在實際運(yùn)用時卻往往需要更加復(fù)雜的產(chǎn)品層次。
在工廠方法模式的一般性結(jié)構(gòu)中,每當(dāng)具體工廠類中的工廠方法被請求時,都會調(diào)用具體產(chǎn)品類的構(gòu)造函數(shù)來創(chuàng)建一個新的產(chǎn)品實例,然后再將這個實例提供給客戶端。但在實際軟件系統(tǒng)中應(yīng)用工廠方法模式時,工廠方法所做的事情可能更加復(fù)雜,其中最常見到的一種情況是循環(huán)使用產(chǎn)品對象。所采用的策略是將工廠對象創(chuàng)建的所有產(chǎn)品對象登記到一個對象池(object pool)中,這樣每當(dāng)客戶請求工廠方法創(chuàng)建相應(yīng)的產(chǎn)品對象時,可以先從對象池中查詢符合條件的產(chǎn)品對象,如果對象池中恰巧有這樣的對象,那就直接將這個產(chǎn)品對象返回給客戶端;如果對象池中沒有這樣的對象,那就創(chuàng)建一個新的滿足要求的產(chǎn)品對象,將其登記到對象池中,然后再返回給客戶端。
工廠方法模式依賴于工廠角色和產(chǎn)品角色的多態(tài)性,但在實際運(yùn)用時這個模式可能出現(xiàn)退化,其表現(xiàn)就是多態(tài)性的喪失。在工廠方法模式中,所有的具體工廠對象應(yīng)該共享一個抽象的超類,或者換句話說,應(yīng)當(dāng)有多個具體工廠類作為一個抽象工廠類的子類存在于工廠等級結(jié)構(gòu)中,但如果工廠等級結(jié)構(gòu)中只有一個具體工廠類的話,那么抽象工廠角色可以省略。當(dāng)抽象工廠角色被省略時,工廠方法模式就發(fā)生了退化,這一退化表現(xiàn)為工廠角色多態(tài)性的喪失,退化后的模式仍然可以發(fā)揮部分工廠方法模式的作用,通常被稱為退化的工廠方法模式。退化的工廠方法模式在很大程度上與簡單工廠模式相似,如圖9所示,實際運(yùn)用時可以考慮用簡單工廠模式進(jìn)行替代。
在工廠方法模式中,從工廠方法返回的應(yīng)當(dāng)是抽象產(chǎn)品類型,而不是具體產(chǎn)品類型,因為只有這樣才能保證產(chǎn)品角色的多態(tài)性。也就是說,調(diào)用工廠方法的客戶端可以針對抽象產(chǎn)品類進(jìn)行編程,而不必依賴于具體產(chǎn)品類。在實際運(yùn)用時有可能會出現(xiàn)一種很特殊的情況,那就是工廠方法只需要返回一個具體產(chǎn)品類,此時工廠方法模式的功能同樣會發(fā)生退化,但這一退化將表現(xiàn)為產(chǎn)品角色多態(tài)性的喪失,如圖10所示。嚴(yán)格說來,當(dāng)工廠方法模式出現(xiàn)這一退化時,就不能再稱為工廠方法模式了,因為客戶端從工廠方法的靜態(tài)類型就可以判斷出將要得到的是什么類型的對象,而這一點恰好違背了工廠方法模式的初衷。
五、優(yōu)勢和不足
在工廠方法模式中,工廠方法用來創(chuàng)建客戶所需要的產(chǎn)品,同時還向客戶隱藏了哪種具體產(chǎn)品類將被實例化這一細(xì)節(jié)。工廠方法模式的核心是一個抽象工廠類,各種具體工廠類通過從抽象工廠類中將工廠方法繼承下來,使得客戶可以只關(guān)心抽象產(chǎn)品和抽象工廠,完全不用理會返回的是哪一種具體產(chǎn)品,也不用關(guān)心它是如何被具體工廠創(chuàng)建的。
基于工廠角色和產(chǎn)品角色的多態(tài)性設(shè)計是工廠方法模式的關(guān)鍵,它使得工廠可以自主確定創(chuàng)建何種產(chǎn)品對象,而如何創(chuàng)建這個對象的細(xì)節(jié)則完全封裝在具體工廠內(nèi)部。工廠方法模式之所以又被稱為多態(tài)工廠模式,顯然是因為所有的具體工廠類都具有同一抽象父類。
使用工廠方法模式的另一個優(yōu)點是在系統(tǒng)中加入新產(chǎn)品時,不需要對抽象工廠和抽象產(chǎn)品提供的接口進(jìn)行修改,而只要添加一個具體工廠和具體產(chǎn)品就可以了,沒有必要修改客戶端,也沒有必須修改其他的具體工廠和具體產(chǎn)品,系統(tǒng)的可擴(kuò)展性非常好。優(yōu)秀的面向?qū)ο笤O(shè)計鼓勵使用封裝(Encapsulation)和委托(Delegation)來構(gòu)造軟件系統(tǒng),而工廠方法模式則是使用了封裝和委托的典型例子,其中封裝是通過抽象工廠來體現(xiàn)的,而委托則是通過抽象工廠將創(chuàng)建對象的責(zé)任完全交給具體工廠來體現(xiàn)的。
使用工廠方法模式的缺點是在添加新產(chǎn)品時,需要編寫新的具體產(chǎn)品類,而且還要提供與之對應(yīng)的具體工廠類,當(dāng)兩者都比較簡單時,系統(tǒng)的額外開銷相對較大。
以上是“Python中工廠方法模式有什么用”這篇文章的所有內(nèi)容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內(nèi)容對大家有所幫助,如果還想學(xué)習(xí)更多知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道!