自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Python設(shè)計(jì)模式:工廠方法模式初探

開(kāi)發(fā) 后端
文章講述如何在用Python開(kāi)發(fā)軟件時(shí)應(yīng)用各種設(shè)計(jì)模式,介紹的是創(chuàng)建型工廠方法(Factory Method)模式,工廠方法(Factory Method)模式又稱為虛擬構(gòu)造器模式或者多態(tài)工廠模式,屬于類的創(chuàng)建型模式。

軟件設(shè)計(jì)大師總是要比初學(xué)者更加清楚該如何設(shè)計(jì)軟件,因?yàn)樗麄兪种姓莆罩O(shè)計(jì)模式這一法寶。作為一種高級(jí)的軟件復(fù)用形式,設(shè)計(jì)模式是眾多優(yōu)秀軟件設(shè)計(jì)師集體智慧的結(jié)晶,能夠很好地指導(dǎo)軟件設(shè)計(jì)過(guò)程。

51CTO推薦閱讀:Python設(shè)計(jì)模式:用模式改變軟件設(shè)計(jì)

工廠方法(Factory Method)模式又稱為虛擬構(gòu)造器(Virtual Constructor)模式或者多態(tài)工廠(Polymorphic Factory)模式,屬于類的創(chuàng)建型模式。在工廠方法模式中,父類負(fù)責(zé)定義創(chuàng)建對(duì)象的公共接口,而子類則負(fù)責(zé)生成具體的對(duì)象,這樣做的目的是將類的實(shí)例化操作延遲到子類中完成,即由子類來(lái)決定究竟應(yīng)該實(shí)體化哪一個(gè)類。

在簡(jiǎn)單工廠模式中,一個(gè)工廠類處于對(duì)產(chǎn)品類進(jìn)行實(shí)例化的中心位置上,它知道每一個(gè)產(chǎn)品類的細(xì)節(jié),并決定何時(shí)哪一個(gè)產(chǎn)品類應(yīng)當(dāng)被實(shí)例化。簡(jiǎn)單工廠模式的優(yōu)點(diǎn)是能夠使客戶端獨(dú)立于產(chǎn)品的創(chuàng)建過(guò)程,并且在系統(tǒng)中引入新產(chǎn)品時(shí)無(wú)需對(duì)客戶端進(jìn)行修改,缺點(diǎn)是當(dāng)有新產(chǎn)品要加入到系統(tǒng)中時(shí),必須對(duì)工廠類進(jìn)行修改,以加入必要的處理邏輯。簡(jiǎn)單工廠模式的致命弱點(diǎn)就是處于核心地位的工廠類,因?yàn)橐坏┧鼰o(wú)法確定要對(duì)哪個(gè)類進(jìn)行實(shí)例化時(shí),就無(wú)法使用該模式,而工廠方法模式則可以很好地避免這一問(wèn)題。

考慮這樣一個(gè)應(yīng)用程序框架(Framework),它可以用來(lái)瀏覽各種格式的文檔,如TXT、DOC、PDF、HTML等,設(shè)計(jì)時(shí)為了讓軟件的體系結(jié)構(gòu)能夠盡可能地通用,定義了Application和Document這兩個(gè)抽象父類,客戶必須通過(guò)它們的子類來(lái)處理某一具體類型的文檔。例如,要想利用該框架來(lái)編寫(xiě)一個(gè)PDF文件瀏覽器,必須先定義PDFApplication和PDFDocument這兩個(gè)類,它們應(yīng)該分別繼承于Application和Document。

Application的職責(zé)是對(duì)Document進(jìn)行管理,并且在需要時(shí)創(chuàng)建它們,比如當(dāng)用戶從菜單中選擇Open或者New的時(shí)候,Application就要負(fù)責(zé)創(chuàng)建一個(gè)Document的實(shí)例。顯而易見(jiàn),被實(shí)例化的特定Document子類是與具體應(yīng)用相關(guān)的,因此Application無(wú)法預(yù)測(cè)哪個(gè)Document的子類將被實(shí)例化,它只知道一個(gè)新的Document何時(shí)(When)被創(chuàng)建,但并不知道哪種(Which)具體的Document將被創(chuàng)建。此時(shí)若仍堅(jiān)持使用簡(jiǎn)單工廠模式會(huì)出現(xiàn)一個(gè)非常尷尬的局面:框架必須實(shí)例化類,但它只知道不能被實(shí)例化的抽象類。

解決的辦法是使用工廠方法模式,它封裝了哪一個(gè)Document子類將被創(chuàng)建的信息,并且能夠?qū)⑦@些信息從框架中分離出來(lái)。如圖1所示,Application的子類重新定義了Application的抽象方法createDocument(),并返回某個(gè)恰當(dāng)?shù)腄ocument子類的實(shí)例。我們稱createDocument()是一個(gè)工廠方法(factory method),因?yàn)樗浅P蜗蟮孛枋隽祟惖膶?shí)例化過(guò)程,即負(fù)責(zé)"生產(chǎn)"一個(gè)對(duì)象。

Application的子類重新定義

簡(jiǎn)單說(shuō)來(lái),工廠方法模式的作用就是可以根據(jù)不同的條件生成各種類的實(shí)例,這些實(shí)例通常屬于多個(gè)相似的類型,并且具有共同的父類。工廠方法模式將這些實(shí)例的創(chuàng)建過(guò)程封裝了起來(lái),從而簡(jiǎn)化了客戶程序的編寫(xiě),并改善了軟件體系結(jié)構(gòu)的可擴(kuò)展性,使得將來(lái)能夠以最小的代價(jià)加入新的子類。工廠方法這一模式適合在如下場(chǎng)合中運(yùn)用:

◆當(dāng)無(wú)法得知必須創(chuàng)建的對(duì)象屬于哪個(gè)類的時(shí)候,或者無(wú)法得知屬于哪個(gè)類的對(duì)象將被返回的時(shí)候,但前提是這些對(duì)象都符合一定的接口標(biāo)準(zhǔn)。

◆當(dāng)一個(gè)類希望由它的子類來(lái)決定所創(chuàng)建的對(duì)象的時(shí)候,其目的是使程序的可擴(kuò)展性更好,在加入其他類時(shí)更具彈性。

◆當(dāng)創(chuàng)建對(duì)象的職責(zé)被委托給多個(gè)幫助子類(helper subclass)中的某一個(gè),并且希望將哪個(gè)子類是代理者這一信息局部化的時(shí)候。

需要說(shuō)明的是,使用工廠方法模式創(chuàng)建對(duì)象并不意味著一定會(huì)讓代碼變得更短(實(shí)事上往往更長(zhǎng)),并且可能需要設(shè)計(jì)更多的輔助類,但它的確可以靈活地、有彈性地創(chuàng)建尚未確定的對(duì)象,從而簡(jiǎn)化了客戶端應(yīng)用程序的邏輯結(jié)構(gòu),并提高了代碼的可讀性和可重用性。

#p#

二、模式引入

工廠方法這一模式本身雖然并不復(fù)雜,但卻是最重要的設(shè)計(jì)模式之一,無(wú)論是在COM、CORBA或是EJB中,都可以隨處見(jiàn)到它的身影。面向?qū)ο蟮囊粋€(gè)基本思想是在不同的對(duì)象間進(jìn)行責(zé)權(quán)的合理分配,從本質(zhì)上講,工廠方法模式是一種用來(lái)創(chuàng)建對(duì)象的多態(tài)方法(polymorphic method),它在抽象父類中聲明用來(lái)創(chuàng)建對(duì)象的方法接口,而具體子類則通過(guò)覆蓋該方法將對(duì)象的創(chuàng)建過(guò)程局部化,包括是否實(shí)例化一個(gè)子類,以及是否對(duì)它進(jìn)行初始化等等。從某種程度上說(shuō),工廠方法可以看成是構(gòu)造函數(shù)的特殊化,其特殊性表現(xiàn)在能夠用一致的方法來(lái)創(chuàng)建不同的對(duì)象,而不用擔(dān)心當(dāng)前正在對(duì)哪個(gè)類進(jìn)行實(shí)例化,因?yàn)榫烤箘?chuàng)建哪個(gè)類的對(duì)象將取決于它的子類。

假設(shè)我們打算開(kāi)發(fā)一個(gè)用于個(gè)人信息管理(Personal Information Manager,PIM)的軟件,它可以保存日常工作和生活中所需的各種信息,包括地址本、電話簿、約會(huì)提醒、日程安排等等。很顯然,PIM用戶界面(User Interface)的設(shè)計(jì)將是比較復(fù)雜的,因?yàn)楸仨殲槊糠N信息的輸入、驗(yàn)證和修改都提供單獨(dú)的界面,以便同用戶進(jìn)行交互。比較簡(jiǎn)單的做法是在PIM中為各種信息的處理編寫(xiě)相應(yīng)的用戶界面,但代價(jià)是將導(dǎo)致軟件的可擴(kuò)展性非常差,因?yàn)橐坏┙窈笠尤雽?duì)其他信息(比如銀行帳戶)進(jìn)行管理的功能時(shí),就必須對(duì)PIM進(jìn)行修改,添加相應(yīng)的用戶界面,從而最終導(dǎo)致PIM變得越來(lái)越復(fù)雜,結(jié)構(gòu)龐大而難以維護(hù)。改進(jìn)的辦法是將處理各種信息的用戶界面從PIM中分離出來(lái),使PIM不再關(guān)心用戶如何輸入數(shù)據(jù),如何對(duì)用戶輸入進(jìn)行驗(yàn)證,以及用戶如何修改信息等,所有的這些都交由一個(gè)專門(mén)的軟件模塊來(lái)完成,而PIM要做的只是提供一個(gè)對(duì)這些個(gè)人信息進(jìn)行管理的總體框架。

在具體實(shí)現(xiàn)時(shí)可以設(shè)計(jì)一個(gè)通用接口Editable,并且讓所有處理特定個(gè)人信息(如通信地址和電話號(hào)碼)的用戶界面都繼承于它,而PIM則通過(guò)Editable提供的方法getEditor()獲得Editor的一個(gè)實(shí)例,并利用它來(lái)對(duì)用戶輸入進(jìn)行統(tǒng)一的處理。例如,當(dāng)用戶完成輸入之后,PIM可以調(diào)用Editor中的方法getContent()來(lái)獲取用戶輸入的數(shù)據(jù),或者調(diào)用resetUI()來(lái)清除用戶輸入的數(shù)據(jù)。在采用這一體系結(jié)構(gòu)之后,如果要擴(kuò)展PIM的功能,只需添加與之對(duì)應(yīng)的Editable和Editor就可以了,而不用對(duì)PIM本身進(jìn)行修改。

現(xiàn)在離目標(biāo)還有一步之遙,由于Editable和Editor都只是通用的接口,但PIM卻需要對(duì)它們的子類進(jìn)行實(shí)例化,此時(shí)自然應(yīng)該想到運(yùn)用工廠方法模式,為PIM定義一個(gè)EditableFactory接口來(lái)創(chuàng)建Editable的對(duì)象。這樣一來(lái),整個(gè)PIM的體系結(jié)構(gòu)就將如圖2所示。

整個(gè)PIM的體系結(jié)構(gòu)

Editable接口定義了一個(gè)公共的構(gòu)造性方法(builder method)getEditor(),它返回一個(gè)Editor對(duì)象,其完整的代碼如清單1所示。任何一項(xiàng)個(gè)人信息都擁有自己獨(dú)立的用戶界面(Editor),負(fù)責(zé)獲取數(shù)據(jù)并在需要的時(shí)候進(jìn)行修改,而PIM唯一要做事情的只是通過(guò)Editable來(lái)獲得Editor,并利用它來(lái)對(duì)用戶輸入的數(shù)據(jù)進(jìn)行相應(yīng)的操作。

  1. 代碼清單1:editable.py  
  2. class Editable:  
  3.   """ 個(gè)人信息用戶界面的公共接口 """  
  4.   # 獲得個(gè)人信息編輯界面  
  5.   def getEditor(self):  
  6. pass 

Editor接口給出了處理所有個(gè)人信息的公共接口,其完整的代碼如清單2所示。PIM通過(guò)調(diào)用getUI()方法能夠獲得與用戶進(jìn)行交互的UI組件,根據(jù)當(dāng)前正在處理的個(gè)人信息的不同,這些組件可能簡(jiǎn)單到只是一個(gè)文本輸入框,也可以復(fù)雜到是一個(gè)包含了多個(gè)圖形控件(Widget)的對(duì)話框。利用Editor提供的getContent()、commitChanges()和resetUI()方法,PIM還可以獲取、提交或者清空用戶輸入的個(gè)人信息。在引入Editor之后, PIM就能夠從處理特定個(gè)人信息的用戶界面中解脫出來(lái),從而可以將注意力集中在如何對(duì)這些信息進(jìn)行統(tǒng)一管理的問(wèn)題上。

  1. 代碼清單2:editor.py  
  2. class Editor:  
  3.   """ 用戶使用特定的Editor來(lái)編輯個(gè)人信息 """  
  4.   # 獲取代表用戶界面(UI)的對(duì)象  
  5.   def getUI(self):  
  6.     pass  
  7.     
  8.   # 獲取用戶輸入的數(shù)據(jù)  
  9.   def getContent(self):  
  10.     pass  
  11.   # 提交用戶輸入的數(shù)據(jù)  
  12.   def commitChanges(self):  
  13.     pass  
  14.       
  15.   # 清空用戶輸入的數(shù)據(jù)  
  16.   def resetUI(self):  
  17.     pass 

EditableAddress是Editable的一個(gè)具體實(shí)現(xiàn),PIM使用它來(lái)處理個(gè)人地址信息,其完整的代碼如清單3所示。

  1. 代碼清單3:editableaddress.py  
  2. from editor import Editor  
  3. from editable import Editable  
  4. import Tkinter  
  5. class EditableAddress(Editable):  
  6.   """ 用于處理個(gè)人地址信息的Editable """  
  7.     
  8.   # 構(gòu)造函數(shù)  
  9.   def __init__(self, master):  
  10.     self.master = master  
  11.     self.name = "" 
  12.     self.province = "" 
  13.     self.city = "" 
  14.     self.street = "" 
  15.     self.zipcode = "" 
  16.     self.editor = AddressEditor(self)  
  17.       
  18.   # 獲取相關(guān)聯(lián)的Editor  
  19.   def getEditor(self):  
  20.     return self.editor  
  21.     
  22.     
  23. class AddressEditor(Editor, Tkinter.Frame):  
  24.   """ 用于處理個(gè)人地址信息的Editor """  
  25.     
  26.   # 構(gòu)造函數(shù)  
  27.   def __init__(self, owner):  
  28.     Tkinter.Frame.__init__(self, owner.master)  
  29.     self.owner = owner  
  30.     self.name = Tkinter.StringVar()  
  31.     self.province = Tkinter.StringVar()  
  32.     self.city = Tkinter.StringVar()  
  33.     self.street = Tkinter.StringVar()  
  34.     self.zipcode = Tkinter.StringVar()  
  35.     self.createWidgets()  
  36.      
  37.   # 構(gòu)造用戶界面  
  38.   def createWidgets(self):  
  39.     # 姓名  
  40.     nameFrame = Tkinter.Frame(self)  
  41.     nameLabel = Tkinter.Label(nameFrame, text="Name:")  
  42.     nameEntry = Tkinter.Entry(nameFrame, textvariable=self.name)  
  43.     nameLabel.config(anchor=Tkinter.E, width=8pady=3)  
  44.     nameLabel.pack(side=Tkinter.LEFT)  
  45.     nameEntry.pack(side=Tkinter.LEFT)  
  46.     nameFrame.pack()  
  47.       
  48.     # 省份  
  49.     provinceFrame = Tkinter.Frame(self)  
  50.     provinceLabel = Tkinter.Label(provinceFrame, text="Province:")  
  51.     provinceEntry = Tkinter.Entry(provinceFrame, textvariable=self.province)  
  52.     provinceLabel.config(anchor=Tkinter.E, width=8pady=3)  
  53.     provinceLabel.pack(side=Tkinter.LEFT)  
  54.     provinceEntry.pack(side=Tkinter.LEFT)  
  55.     provinceFrame.pack()  
  56.     # 城市  
  57.     cityFrame = Tkinter.Frame(self)  
  58.     cityLabel = Tkinter.Label(cityFrame, text="City:")  
  59.     cityEntry = Tkinter.Entry(cityFrame, textvariable=self.city)  
  60.     cityLabel.config(anchor=Tkinter.E, width=8pady=3)  
  61.     cityLabel.pack(side=Tkinter.LEFT)  
  62.     cityEntry.pack(side=Tkinter.LEFT)  
  63.     cityFrame.pack()  
  64.       
  65.     # 街道  
  66.     streetFrame = Tkinter.Frame(self)  
  67.     streetLabel = Tkinter.Label(streetFrame, text="Street:")  
  68.     streetEntry = Tkinter.Entry(streetFrame, textvariable=self.street)  
  69.     streetLabel.config(anchor=Tkinter.E, width=8pady=3)  
  70.     streetLabel.pack(side=Tkinter.LEFT)  
  71.     streetEntry.pack(side=Tkinter.LEFT)  
  72.     streetFrame.pack()  
  73.       
  74.     # 郵編  
  75.     zipcodeFrame = Tkinter.Frame(self)  
  76.     zipcodeLabel = Tkinter.Label(zipcodeFrame, text="ZIP Code:")  
  77.     zipcodeEntry = Tkinter.Entry(zipcodeFrame, textvariable=self.zipcode)  
  78.     zipcodeLabel.config(anchor=Tkinter.E, width=8pady=3)  
  79.     zipcodeLabel.pack(side=Tkinter.LEFT)  
  80.     zipcodeEntry.pack(side=Tkinter.LEFT)  
  81.     zipcodeFrame.pack()  
  82.       
  83.   # 重載Editor中的方法,獲取代表用戶界面(UI)的對(duì)象  
  84.   def getUI(self):  
  85.     return self  
  86.     
  87.   # 重載Editor中的方法,獲取用戶輸入的數(shù)據(jù)  
  88.   def getContent(self):  
  89.     content  = "    Name: " + self.name.get() + "\n"  
  90.     content += "Province: " + self.province.get() + "\n"   
  91.     content += "    City: " + self.city.get() + "\n"   
  92.     content += "  Street: " + self.street.get() + "\n"   
  93.     content += "ZIP Code: " + self.zipcode.get()  
  94.     return content  
  95.     
  96.   # 重載Editor中的方法,提交用戶輸入的數(shù)據(jù)  
  97.   def commitChanges(self):  
  98.     selfself.owner.name = self.name.get()  
  99.     selfself.owner.province = self.province.get()  
  100.     selfself.owner.city = self.city.get()  
  101.     selfself.owner.street = self.street.get()  
  102.     selfself.owner.zipcode = self.zipcode.get()  
  103.   # 重載Editor中的方法,清空用戶輸入的數(shù)據(jù)  
  104.   def resetUI(self):  
  105.     self.name.set("")  
  106.     self.province.set("")  
  107.     self.city.set("")  
  108.     self.street.set("")  
  109.     self.zipcode.set("")  

EditablePhone是Editable的另一個(gè)具體實(shí)現(xiàn),PIM使用它來(lái)處理個(gè)人電話號(hào)碼,其完整的代碼如清單4所示。

  1. 代碼清單4:editablephone.py  
  2. from editor import Editor  
  3. from editable import Editable  
  4. import Tkinter  
  5. class EditablePhone(Editable):  
  6.   """ 用于處理個(gè)人電話號(hào)碼的Editable """  
  7.     
  8.   # 構(gòu)造函數(shù)  
  9.   def __init__(self, master):  
  10.     self.master =master  
  11.     self.areaCode = "";  
  12.     self.phoneNumber = "" 
  13.     self.editor = PhoneEditor(self)  
  14.       
  15.   # 獲取相關(guān)聯(lián)的Editor  
  16.   def getEditor(self):  
  17.     return self.editor  
  18.     
  19. class PhoneEditor(Editor, Tkinter.Frame):  
  20.   """ 用于處理個(gè)人電話號(hào)碼的Editor """  
  21.     
  22.   # 構(gòu)造函數(shù)  
  23.   def __init__(self, owner):  
  24.     self.owner = owner  
  25.     Tkinter.Frame.__init__(self, self.owner.master)  
  26.     self.areaCode = Tkinter.StringVar()  
  27.     self.phoneNumber = Tkinter.StringVar()  
  28.     # 構(gòu)造用戶界面  
  29.     codeLabel = Tkinter.Label(self, text="Area Code:")  
  30.     codeEntry = Tkinter.Entry(self, textvariable=self.areaCode)  
  31.     codeLabel.config(anchor=Tkinter.E, width=12pady=3)  
  32.     codeLabel.grid(row=0column=0)  
  33.     codeEntry.grid(row=0column=1)  
  34.       
  35.     numberLabel = Tkinter.Label(self, text="Phone Number:")  
  36.     numberEntry = Tkinter.Entry(self, textvariable=self.phoneNumber)  
  37.     numberLabel.config(anchor=Tkinter.E, width=12pady=3)  
  38.     numberLabel.grid(row=1column=0)  
  39.     numberEntry.grid(row=1column=1)  
  40.       
  41.   # 重載Editor中的方法,獲取代表用戶界面(UI)的對(duì)象  
  42.   def getUI(self):  
  43.     return self  
  44.     
  45.   # 重載Editor中的方法,獲取用戶輸入的數(shù)據(jù)  
  46.   def getContent(self):  
  47.     content  = "   Area Code: " + self.areaCode.get() + "\n"  
  48.     content += "Phone Number: " + self.phoneNumber.get() + "\n"   
  49.     return content  
  50.    
  51.   # 重載Editor中的方法,提交用戶輸入的數(shù)據(jù)  
  52.   def commitChanges(self):  
  53.     selfself.owner.areaCode = self.areaCode.get()  
  54.     selfself.owner.phoneNumber = self.phoneNumber.get()  
  55.       
  56.   # 重載Editor中的方法,清空用戶輸入的數(shù)據(jù)  
  57.   def resetUI(self):  
  58.     self.areaCode.set("")  
  59.     self.phoneNumber.set("") 

EditableFactory接口是在PIM中應(yīng)用工廠方法模式的核心,其完整的代碼如清單5所示。與簡(jiǎn)單工廠模式中負(fù)責(zé)創(chuàng)建所有對(duì)象的"超級(jí)類"不同,EditableFactory只定義了如何實(shí)例化Editable的工廠方法createEditable(),并不掌握它們的生殺大權(quán),真正負(fù)責(zé)完成創(chuàng)建工作的是EditableFactory的子類。

  1. 代碼清單5:editablefactory.py  
  2. class EditableFactory:  
  3.   """ 用于創(chuàng)建Editable的工廠類 """  
  4.     
  5.   #  實(shí)例化Editable對(duì)象  
  6.   def createEditable(self, master):  
  7.     pass 

EditableAddressFactory是EditableFactory的一個(gè)具體實(shí)現(xiàn),PIM使用它來(lái)實(shí)例化EditableAddress對(duì)象,其完整的代碼如清單6所示。

  1. 代碼清單6:editableaddressfactory.py  
  2. from editablefactory import EditableFactory  
  3. from editableaddress import EditableAddress  
  4. class EditableAddressFactory(EditableFactory):  
  5.   """ 用于創(chuàng)建EditableAddress的工廠類 """  
  6.     
  7.   # 重載EditableFactory中的方法,實(shí)例化EditableAddress對(duì)象  
  8.   def createEditable(self, master):  
  9.     address = EditableAddress(master)  
  10.     return address 

EditablePhoneFactory是EditableFactory的另一個(gè)具體實(shí)現(xiàn),PIM使用它來(lái)實(shí)例化EditablePhone對(duì)象,其完整的代碼如清單7所示。

  1. 代碼清單7:editablephonefactory.py  
  2. from editablefactory import EditableFactory  
  3. from editablephone import EditablePhone  
  4. class EditablePhoneFactory(EditableFactory):  
  5.   """ 用于創(chuàng)建EditablePhone的工廠類 """  
  6.     
  7.   # 重載EditableFactory中的方法,實(shí)例化EditablePhone對(duì)象  
  8.   def createEditable(self, master):  
  9.     phone = EditablePhone(master)  
  10.     return phone 

所有這些輔助類都定義好之后,接下去就可以編寫(xiě)PIM類了,它提供了一個(gè)對(duì)各種個(gè)人信息進(jìn)行統(tǒng)一管理的框架,其完整的代碼如清單8所示。

  1. 代碼清單8:pim.py  
  2. from editablephone import EditablePhone  
  3. from editableaddressfactory import EditableAddressFactory  
  4. from editablephonefactory import EditablePhoneFactory  
  5. import Tkinter  
  6. class PIM:  
  7.   """ 個(gè)人信息管理 """  
  8.     
  9.   # 構(gòu)造函數(shù)  
  10.   def __init__(self):  
  11.     mainFrame = Tkinter.Frame()      
  12.     mainFrame.master.title("PIM")  
  13.     # 命令按鈕  
  14.     addressButton = Tkinter.Button(mainFrame, width=10text="Address")  
  15.     phoneButton = Tkinter.Button(mainFrame, width=10text="Phone")  
  16.     commitButton = Tkinter.Button(mainFrame, width=10text="Commit")      
  17.     resetButton = Tkinter.Button(mainFrame, width=10text="Reset")          
  18.     addressButton.config(command=self.addressClicked)  
  19.     phoneButton.config(command=self.phoneClicked)      
  20.     commitButton.config(command=self.commitClicked)          
  21.     resetButton.config(command=self.resetClicked)          
  22.     addressButton.grid(row=0column=1padx=10pady=5stick=Tkinter.E)  
  23.     phoneButton.grid(row=1column=1padx=10pady=5stick=Tkinter.E)  
  24.     commitButton.grid(row=2column=1padx=10pady=5stick=Tkinter.E)  
  25.     resetButton.grid(row=3column=1padx=10pady=5stick=Tkinter.E)  
  26.       
  27.  # 用來(lái)容納各類Editor的容器  
  28.     self.editorFrame = Tkinter.Frame(mainFrame)  
  29.     self.editorFrame.grid(row=0column=0rowspan=4)  
  30.     self.editorFrame.grid_configure(stick=Tkinter.N, pady=15)  
  31.     self.editor = Tkinter.Frame(self.editorFrame)  
  32.     self.editor.grid()  
  33.       
  34.  # 個(gè)人信息顯示區(qū)域  
  35.     self.content = Tkinter.StringVar()  
  36.     self.contentLabel = Tkinter.Label(mainFrame, width=50height=5)  
  37.     self.contentLabel.configure(textvariable=self.content)  
  38.     self.contentLabel.configure(anchor=Tkinter.W, font="Arial 10 italic bold")  
  39.     self.contentLabel.configure(relief=Tkinter.RIDGE, pady=5padx=10)  
  40.     self.contentLabel.grid(row=4column=0columnspan=2)  
  41.       
  42.     mainFrame.pack()  
  43.     mainFrame.mainloop()  
  44.       
  45.   # Address按鈕的回調(diào)函數(shù)  
  46.   def addressClicked(self):  
  47.     address = EditableAddressFactory().createEditable(self.editorFrame)  
  48.     self.editor.grid_remove()  
  49.     self.editor = address.getEditor()  
  50.     self.editor.getUI().grid()  
  51.   # Phone按鈕的回調(diào)函數(shù)  
  52.   def phoneClicked(self):  
  53.     phone = EditablePhoneFactory().createEditable(self.editorFrame)  
  54.     self.editor.grid_remove()  
  55.     self.editor = phone.getEditor()  
  56.     self.editor.getUI().grid()  
  57.       
  58.   # Commit按鈕的回調(diào)函數(shù)  
  59.   def commitClicked(self):  
  60.     content = self.editor.getContent()  
  61.     self.content.set(content)  
  62.     
  63.   # Reset按鈕的回調(diào)函數(shù)  
  64.   def resetClicked(self):  
  65.     self.editor.resetUI()  
  66.       
  67. # 主函數(shù)  
  68. if (__name__ == "__main__"):  
  69.   app = PIM() 

圖3是PIM在運(yùn)行時(shí)的界面效果。

PIM在運(yùn)行時(shí)的界面效果

#p#

三、一般結(jié)構(gòu)

工廠方法模式是簡(jiǎn)單工廠模式的進(jìn)一步抽象和推廣,它不僅保持了簡(jiǎn)單工廠模式能夠向客戶隱藏類的實(shí)例化過(guò)程這一優(yōu)點(diǎn),而且還通過(guò)多態(tài)性克服了工廠類過(guò)于復(fù)雜且不易于擴(kuò)展的缺點(diǎn)。在工廠方法模式中,處于核心地位的工廠類不再負(fù)責(zé)所有產(chǎn)品的創(chuàng)建,而是將具體的創(chuàng)建工作交由子類去完成。工廠方法模式中的核心工廠類經(jīng)過(guò)功能抽象之后,成為了一個(gè)抽象的工廠角色,僅負(fù)責(zé)給出具體工廠子類必須實(shí)現(xiàn)的接口,而不涉及哪種產(chǎn)品類應(yīng)當(dāng)被實(shí)例化這一細(xì)節(jié)。工廠方法模式的一般性結(jié)構(gòu)如圖4所示,圖中為了簡(jiǎn)化只給出了一個(gè)產(chǎn)品類和一個(gè)工廠類,但在實(shí)際系統(tǒng)中通常需要設(shè)計(jì)多個(gè)產(chǎn)品類和多個(gè)工廠類。

一般結(jié)構(gòu)

工廠方法模式的實(shí)質(zhì)是將對(duì)象的創(chuàng)建延遲到其子類實(shí)現(xiàn),即由子類根據(jù)當(dāng)前情況動(dòng)態(tài)決定應(yīng)該實(shí)例化哪一個(gè)產(chǎn)品類。從上圖可以看出,工廠方法模式涉及到抽象工廠角色、具體工廠角色、抽象產(chǎn)品角色和具體產(chǎn)品角色四個(gè)參與者。

◆抽象工廠(Creator)角色:是工廠方法模式的核心,它負(fù)責(zé)定義創(chuàng)建抽象產(chǎn)品對(duì)象的工廠方法。抽象工廠不能被外界直接調(diào)用,但任何在模式中用于創(chuàng)建產(chǎn)品對(duì)象的工廠類都必須實(shí)現(xiàn)由它所定義的工廠方法。

具體工廠(Concrete Creator)角色:是工廠方法模式的對(duì)外接口,它負(fù)責(zé)實(shí)現(xiàn)創(chuàng)建具體產(chǎn)品對(duì)象的內(nèi)部邏輯。具體工廠與應(yīng)用密切相關(guān),可以被外界直接調(diào)用,創(chuàng)建所需要的產(chǎn)品。

抽象產(chǎn)品(Product)角色:是工廠方法模式所創(chuàng)建的所有對(duì)象的父類,它負(fù)責(zé)描述所有具體產(chǎn)品共有的公共接口。

具體產(chǎn)品(Concrete Product)角色:是工廠方法模式的創(chuàng)建目標(biāo),所有創(chuàng)建的對(duì)象都是充當(dāng)這一角色的某個(gè)具體類的實(shí)例。

抽象工廠角色負(fù)責(zé)聲明工廠方法(factory method),用來(lái)"生產(chǎn)"抽象產(chǎn)品,以下是抽象工廠的示例性Python代碼:

  1. 代碼清單9:creator.py  
  2. class Creator:  
  3.   """ 抽象工廠角色 """  
  4.     
  5.   # 創(chuàng)建抽象產(chǎn)品的工廠方法  
  6.   def factoryMethod(self):  
  7.     pass 

具體工廠角色負(fù)責(zé)創(chuàng)建一個(gè)具體產(chǎn)品的實(shí)例,并將其返回給調(diào)用者。具體工廠是與具體產(chǎn)品相關(guān)的,實(shí)現(xiàn)時(shí)一般常用的做法是為每個(gè)具體產(chǎn)品定義一個(gè)具體工廠。以下是具體工廠的示例性Python代碼:

  1. 代碼清單10:concretecreator.py  
  2. class ConcreteCreator(Creator):  
  3.   """ 具體工廠角色 """  
  4.     
  5.   # 創(chuàng)建具體產(chǎn)品的工廠方法  
  6.   def factoryMethod(self):  
  7.     product =  ConcreteProduct()  
  8.     return product 

抽象產(chǎn)品角色的主要目的是為所有的具體產(chǎn)品提供一個(gè)共同的接口,通常只需給出相應(yīng)的聲明就可以了,而不用給出具體的實(shí)現(xiàn)。以下是抽象產(chǎn)品類的示例性Python代碼:

  1. 代碼清單11:product.py  
  2. class Product:  
  3.   """ 抽象產(chǎn)品角色 """  
  4.     
  5.   # 所有產(chǎn)品類的公共接口  
  6.   def interface(self):  
  7.     pass 

具體產(chǎn)品角色充當(dāng)最終的創(chuàng)建目標(biāo),一般來(lái)講它是抽象產(chǎn)品類的子類,實(shí)現(xiàn)了抽象產(chǎn)品類中定義的所有工廠方法,實(shí)際應(yīng)用時(shí)通常會(huì)具有比較復(fù)雜的業(yè)務(wù)邏輯。以下是具體產(chǎn)品類的示例性Python代碼:

  1. 代碼清單12:concreteproduct.py  
  2. class ConcreteProduct(Product):  
  3.   """ 具體產(chǎn)品角色 """  
  4.     
  5.   # 公共接口的實(shí)現(xiàn)  
  6.   def interface(self):  
  7.     print "Concrete Product Method" 

    
在應(yīng)用工廠方法模式時(shí),通常還需要再引入一個(gè)客戶端角色,由它負(fù)責(zé)創(chuàng)建具體的工廠對(duì)象,然后再調(diào)用工廠對(duì)象中的工廠方法來(lái)創(chuàng)建相應(yīng)的產(chǎn)品對(duì)象。以下是客戶端的示例性Python代碼:

  1. 代碼清單13:client.py  
  2. class Client:  
  3.   """ 客戶端角色 """  
  4.     
  5.   def run(self):  
  6.     creator = ConcreteCreator()  
  7.     product = creator.factoryMethod()  
  8.     product.interface()  
  9. # 主函數(shù)  
  10. if (__name__ == "__main__"):  
  11.   client = Client()  
  12.   client.run() 

  
在這個(gè)簡(jiǎn)單的示意性實(shí)現(xiàn)里,充當(dāng)具體產(chǎn)品和具體工廠角色的類都只有一個(gè),但在真正的實(shí)際應(yīng)用中,通常遇到的都是同時(shí)會(huì)有多個(gè)具體產(chǎn)品類的情況,此時(shí)相應(yīng)地需要提供多個(gè)具體工廠類,每個(gè)具體工廠都負(fù)責(zé)生產(chǎn)對(duì)應(yīng)的具體產(chǎn)品。

工廠方法模式的活動(dòng)序列如圖5所示,客戶端Client首先創(chuàng)建ConcreteCreator對(duì)象,然后調(diào)用ConcreteCreator對(duì)象的工廠方法factoryMethod(),由它負(fù)責(zé)"生產(chǎn)"出所需要的ConcreteProduct對(duì)象。

工廠方法模式的活動(dòng)序列

#p#

四、實(shí)際運(yùn)用

使用工廠方法模式可以在不修改具體工廠角色的情況下引入新的產(chǎn)品,這一點(diǎn)無(wú)疑使得工廠方法模式具有比簡(jiǎn)單工廠模式更好的可擴(kuò)展性。在開(kāi)發(fā)實(shí)際的軟件系統(tǒng)時(shí),通常是先設(shè)計(jì)產(chǎn)品角色,然后才開(kāi)始設(shè)計(jì)工廠角色,而復(fù)雜的需求導(dǎo)致將在抽象產(chǎn)品和具體產(chǎn)品之間形成非常龐大的樹(shù)狀結(jié)構(gòu),如圖6所示。

如圖6

在上面的產(chǎn)品等級(jí)結(jié)構(gòu)中,出現(xiàn)了多于一個(gè)的抽象產(chǎn)品類,以及多于兩個(gè)的類層次,這是在構(gòu)造真實(shí)系統(tǒng)中經(jīng)常遇到的情況。在為這一軟件體系結(jié)構(gòu)應(yīng)用工廠方法模式時(shí),通常的做法是按照產(chǎn)品的等級(jí)結(jié)構(gòu)再設(shè)計(jì)一個(gè)相同的工廠等級(jí)結(jié)構(gòu),如圖7所示。

如圖7

定義工廠角色的目的是為了創(chuàng)建相應(yīng)的產(chǎn)品角色,因此整個(gè)系統(tǒng)的架構(gòu)將如圖8所示。這一結(jié)構(gòu)常常被稱為平行的類層次(parallel class hierarchies),它使得一個(gè)類能夠?qū)⑺囊恍┞氊?zé)委托給另一個(gè)獨(dú)立的類,而工廠方法則是聯(lián)系兩者之間的紐帶。工廠方法模式并沒(méi)有限制產(chǎn)品等級(jí)的層數(shù),雖然前面給出的一般性結(jié)構(gòu)中只有兩個(gè)層次(抽象產(chǎn)品層和具體產(chǎn)品層),但在實(shí)際運(yùn)用時(shí)卻往往需要更加復(fù)雜的產(chǎn)品層次。

如圖8

在工廠方法模式的一般性結(jié)構(gòu)中,每當(dāng)具體工廠類中的工廠方法被請(qǐng)求時(shí),都會(huì)調(diào)用具體產(chǎn)品類的構(gòu)造函數(shù)來(lái)創(chuàng)建一個(gè)新的產(chǎn)品實(shí)例,然后再將這個(gè)實(shí)例提供給客戶端。但在實(shí)際軟件系統(tǒng)中應(yīng)用工廠方法模式時(shí),工廠方法所做的事情可能更加復(fù)雜,其中最常見(jiàn)到的一種情況是循環(huán)使用產(chǎn)品對(duì)象。所采用的策略是將工廠對(duì)象創(chuàng)建的所有產(chǎn)品對(duì)象登記到一個(gè)對(duì)象池(object pool)中,這樣每當(dāng)客戶請(qǐng)求工廠方法創(chuàng)建相應(yīng)的產(chǎn)品對(duì)象時(shí),可以先從對(duì)象池中查詢符合條件的產(chǎn)品對(duì)象,如果對(duì)象池中恰巧有這樣的對(duì)象,那就直接將這個(gè)產(chǎn)品對(duì)象返回給客戶端;如果對(duì)象池中沒(méi)有這樣的對(duì)象,那就創(chuàng)建一個(gè)新的滿足要求的產(chǎn)品對(duì)象,將其登記到對(duì)象池中,然后再返回給客戶端。

工廠方法模式依賴于工廠角色和產(chǎn)品角色的多態(tài)性,但在實(shí)際運(yùn)用時(shí)這個(gè)模式可能出現(xiàn)退化,其表現(xiàn)就是多態(tài)性的喪失。在工廠方法模式中,所有的具體工廠對(duì)象應(yīng)該共享一個(gè)抽象的超類,或者換句話說(shuō),應(yīng)當(dāng)有多個(gè)具體工廠類作為一個(gè)抽象工廠類的子類存在于工廠等級(jí)結(jié)構(gòu)中,但如果工廠等級(jí)結(jié)構(gòu)中只有一個(gè)具體工廠類的話,那么抽象工廠角色可以省略。當(dāng)抽象工廠角色被省略時(shí),工廠方法模式就發(fā)生了退化,這一退化表現(xiàn)為工廠角色多態(tài)性的喪失,退化后的模式仍然可以發(fā)揮部分工廠方法模式的作用,通常被稱為退化的工廠方法模式。退化的工廠方法模式在很大程度上與簡(jiǎn)單工廠模式相似,如圖9所示,實(shí)際運(yùn)用時(shí)可以考慮用簡(jiǎn)單工廠模式進(jìn)行替代。

如圖9

在工廠方法模式中,從工廠方法返回的應(yīng)當(dāng)是抽象產(chǎn)品類型,而不是具體產(chǎn)品類型,因?yàn)橹挥羞@樣才能保證產(chǎn)品角色的多態(tài)性。也就是說(shuō),調(diào)用工廠方法的客戶端可以針對(duì)抽象產(chǎn)品類進(jìn)行編程,而不必依賴于具體產(chǎn)品類。在實(shí)際運(yùn)用時(shí)有可能會(huì)出現(xiàn)一種很特殊的情況,那就是工廠方法只需要返回一個(gè)具體產(chǎn)品類,此時(shí)工廠方法模式的功能同樣會(huì)發(fā)生退化,但這一退化將表現(xiàn)為產(chǎn)品角色多態(tài)性的喪失,如圖10所示。嚴(yán)格說(shuō)來(lái),當(dāng)工廠方法模式出現(xiàn)這一退化時(shí),就不能再稱為工廠方法模式了,因?yàn)榭蛻舳藦墓S方法的靜態(tài)類型就可以判斷出將要得到的是什么類型的對(duì)象,而這一點(diǎn)恰好違背了工廠方法模式的初衷。

如圖10

五、優(yōu)勢(shì)和不足

在工廠方法模式中,工廠方法用來(lái)創(chuàng)建客戶所需要的產(chǎn)品,同時(shí)還向客戶隱藏了哪種具體產(chǎn)品類將被實(shí)例化這一細(xì)節(jié)。工廠方法模式的核心是一個(gè)抽象工廠類,各種具體工廠類通過(guò)從抽象工廠類中將工廠方法繼承下來(lái),使得客戶可以只關(guān)心抽象產(chǎn)品和抽象工廠,完全不用理會(huì)返回的是哪一種具體產(chǎn)品,也不用關(guān)心它是如何被具體工廠創(chuàng)建的。

基于工廠角色和產(chǎn)品角色的多態(tài)性設(shè)計(jì)是工廠方法模式的關(guān)鍵,它使得工廠可以自主確定創(chuàng)建何種產(chǎn)品對(duì)象,而如何創(chuàng)建這個(gè)對(duì)象的細(xì)節(jié)則完全封裝在具體工廠內(nèi)部。工廠方法模式之所以又被稱為多態(tài)工廠模式,顯然是因?yàn)樗械木唧w工廠類都具有同一抽象父類。

使用工廠方法模式的另一個(gè)優(yōu)點(diǎn)是在系統(tǒng)中加入新產(chǎn)品時(shí),不需要對(duì)抽象工廠和抽象產(chǎn)品提供的接口進(jìn)行修改,而只要添加一個(gè)具體工廠和具體產(chǎn)品就可以了,沒(méi)有必要修改客戶端,也沒(méi)有必須修改其他的具體工廠和具體產(chǎn)品,系統(tǒng)的可擴(kuò)展性非常好。優(yōu)秀的面向?qū)ο笤O(shè)計(jì)鼓勵(lì)使用封裝(Encapsulation)和委托(Delegation)來(lái)構(gòu)造軟件系統(tǒng),而工廠方法模式則是使用了封裝和委托的典型例子,其中封裝是通過(guò)抽象工廠來(lái)體現(xiàn)的,而委托則是通過(guò)抽象工廠將創(chuàng)建對(duì)象的責(zé)任完全交給具體工廠來(lái)體現(xiàn)的。

使用工廠方法模式的缺點(diǎn)是在添加新產(chǎn)品時(shí),需要編寫(xiě)新的具體產(chǎn)品類,而且還要提供與之對(duì)應(yīng)的具體工廠類,當(dāng)兩者都比較簡(jiǎn)單時(shí),系統(tǒng)的額外開(kāi)銷相對(duì)較大。

六、小結(jié)

工廠方法模式的核心思想是定義一個(gè)用來(lái)創(chuàng)建對(duì)象的公共接口,由工廠而不是客戶來(lái)決定需要被實(shí)例化的類,它通常在構(gòu)造系統(tǒng)整體框架時(shí)被用到。工廠方法模式看上去似乎比較簡(jiǎn)單,但是內(nèi)涵卻極其深刻,抽象、封裝、繼承、委托、多態(tài)等面向?qū)ο笤O(shè)計(jì)中的理論都得到了很好的體現(xiàn),應(yīng)用范圍非常廣泛。

原文鏈接:http://www.ibm.com/developerworks/cn/linux/l-pypt/part3/index.html

【編輯推薦】

  1. 全能選手 看看Python應(yīng)乎潮流的72變
  2. 匪夷所思 Python實(shí)現(xiàn)尾遞歸優(yōu)化
  3. Python設(shè)計(jì)模式:用模式改變軟件設(shè)計(jì)
  4. Python閉包的概念、形式與應(yīng)用
  5. 旁觀者清 Python與Ruby各有千秋
責(zé)任編輯:王曉東 來(lái)源: IBM
相關(guān)推薦

2013-11-26 16:29:22

Android設(shè)計(jì)模式

2009-01-15 10:55:29

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

2020-08-11 11:20:30

Typescript設(shè)計(jì)模式

2024-02-20 12:09:32

模式工廠方法接口

2023-08-05 13:31:20

工廠方法模式對(duì)象

2022-01-12 13:33:25

工廠模式設(shè)計(jì)

2023-09-11 08:30:30

Creator工廠方法

2021-03-06 22:50:58

設(shè)計(jì)模式抽象

2020-08-21 07:23:50

工廠模式設(shè)計(jì)

2011-11-17 16:03:05

Java工廠模式Clojure

2020-10-19 09:28:00

抽象工廠模式

2021-09-29 13:53:17

抽象工廠模式

2010-04-19 09:30:00

工廠模式PHP設(shè)計(jì)模式

2023-12-12 11:09:55

模板方法模式python設(shè)計(jì)模式

2023-12-26 08:20:40

2020-09-23 06:52:49

代碼方法模式

2024-03-06 13:19:19

工廠模式Python函數(shù)

2024-07-31 08:12:33

2022-05-09 08:04:50

工廠模式設(shè)計(jì)模式

2011-07-28 09:50:58

設(shè)計(jì)模式
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)