Python設計模式系列之三: 創建型Factory Method模式

一、簡介

工廠方法(Factory Method)模式又稱爲虛擬構造器(Virtual Constructor)模式或者多態工廠(Polymorphic Factory)模式,屬於類的創建型模式。在工廠方法模式中,父類負責定義創建對象的公共接口,而子類則負責生成具體的對象,這樣做的目的是將類的實例化操作延遲到子類中完成,即由子類來決定究竟應該實體化哪一個類。

在簡單工廠模式中,一個工廠類處於對產品類進行實例化的中心位置上,它知道每一個產品類的細節,並決定何時哪一個產品類應當被實例化。簡單工廠模式的優點是能夠使客戶端獨立於產品的創建過程,並且在系統中引入新產品時無需對客戶端進行修改,缺點是當有新產品要加入到系統中時,必須對工廠類進行修改,以加入必要的處理邏輯。簡單工廠模式的致命弱點就是處於核心地位的工廠類,因爲一旦它無法確定要對哪個類進行實例化時,就無法使用該模式,而工廠方法模式則可以很好地避免這一問題。

考慮這樣一個應用程序框架(Framework),它可以用來瀏覽各種格式的文檔,如TXT、DOC、PDF、HTML等,設計時爲了讓軟件的體系結構能夠儘可能地通用,定義了Application和Document這兩個抽象父類,客戶必須通過它們的子類來處理某一具體類型的文檔。例如,要想利用該框架來編寫一個PDF文件瀏覽器,必須先定義PDFApplication和PDFDocument這兩個類,它們應該分別繼承於Application和Document。

Application的職責是對Document進行管理,並且在需要時創建它們,比如當用戶從菜單中選擇Open或者New的時候,Application就要負責創建一個Document的實例。顯而易見,被實例化的特定Document子類是與具體應用相關的,因此Application無法預測哪個Document的子類將被實例化,它只知道一個新的Document何時(When)被創建,但並不知道哪種(Which)具體的Document將被創建。此時若仍堅持使用簡單工廠模式會出現一個非常尷尬的局面:框架必須實例化類,但它只知道不能被實例化的抽象類。

解決的辦法是使用工廠方法模式,它封裝了哪一個Document子類將被創建的信息,並且能夠將這些信息從框架中分離出來。如圖1所示,Application的子類重新定義了Application的抽象方法createDocument(),並返回某個恰當的Document子類的實例。我們稱createDocument()是一個工廠方法(factory method),因爲它非常形象地描述了類的實例化過程,即負責"生產"一個對象。

圖1
圖1

簡單說來,工廠方法模式的作用就是可以根據不同的條件生成各種類的實例,這些實例通常屬於多個相似的類型,並且具有共同的父類。工廠方法模式將這些實例的創建過程封裝了起來,從而簡化了客戶程序的編寫,並改善了軟件體系結構的可擴展性,使得將來能夠以最小的代價加入新的子類。工廠方法這一模式適合在如下場合中運用:

  • 當無法得知必須創建的對象屬於哪個類的時候,或者無法得知屬於哪個類的對象將被返回的時候,但前提是這些對象都符合一定的接口標準。
  • 當一個類希望由它的子類來決定所創建的對象的時候,其目的是使程序的可擴展性更好,在加入其他類時更具彈性。
  • 當創建對象的職責被委託給多個幫助子類(helper subclass)中的某一個,並且希望將哪個子類是代理者這一信息局部化的時候。

需要說明的是,使用工廠方法模式創建對象並不意味着一定會讓代碼變得更短(實事上往往更長),並且可能需要設計更多的輔助類,但它的確可以靈活地、有彈性地創建尚未確定的對象,從而簡化了客戶端應用程序的邏輯結構,並提高了代碼的可讀性和可重用性。

二、模式引入

工廠方法這一模式本身雖然並不複雜,但卻是最重要的設計模式之一,無論是在COM、CORBA或是EJB中,都可以隨處見到它的身影。面向對象的一個基本思想是在不同的對象間進行責權的合理分配,從本質上講,工廠方法模式是一種用來創建對象的多態方法(polymorphic method),它在抽象父類中聲明用來創建對象的方法接口,而具體子類則通過覆蓋該方法將對象的創建過程局部化,包括是否實例化一個子類,以及是否對它進行初始化等等。從某種程度上說,工廠方法可以看成是構造函數的特殊化,其特殊性表現在能夠用一致的方法來創建不同的對象,而不用擔心當前正在對哪個類進行實例化,因爲究竟創建哪個類的對象將取決於它的子類。

假設我們打算開發一個用於個人信息管理(Personal Information Manager,PIM)的軟件,它可以保存日常工作和生活中所需的各種信息,包括地址本、電話簿、約會提醒、日程安排等等。很顯然,PIM用戶界面(User Interface)的設計將是比較複雜的,因爲必須爲每種信息的輸入、驗證和修改都提供單獨的界面,以便同用戶進行交互。比較簡單的做法是在PIM中爲各種信息的處理編寫相應的用戶界面,但代價是將導致軟件的可擴展性非常差,因爲一旦今後要加入對其他信息(比如銀行帳戶)進行管理的功能時,就必須對PIM進行修改,添加相應的用戶界面,從而最終導致PIM變得越來越複雜,結構龐大而難以維護。改進的辦法是將處理各種信息的用戶界面從PIM中分離出來,使PIM不再關心用戶如何輸入數據,如何對用戶輸入進行驗證,以及用戶如何修改信息等,所有的這些都交由一個專門的軟件模塊來完成,而PIM要做的只是提供一個對這些個人信息進行管理的總體框架。

在具體實現時可以設計一個通用接口Editable,並且讓所有處理特定個人信息(如通信地址和電話號碼)的用戶界面都繼承於它,而PIM則通過Editable提供的方法getEditor()獲得Editor的一個實例,並利用它來對用戶輸入進行統一的處理。例如,當用戶完成輸入之後,PIM可以調用Editor中的方法getContent()來獲取用戶輸入的數據,或者調用resetUI()來清除用戶輸入的數據。在採用這一體系結構之後,如果要擴展PIM的功能,只需添加與之對應的Editable和Editor就可以了,而不用對PIM本身進行修改。

現在離目標還有一步之遙,由於Editable和Editor都只是通用的接口,但PIM卻需要對它們的子類進行實例化,此時自然應該想到運用工廠方法模式,爲PIM定義一個EditableFactory接口來創建Editable的對象。這樣一來,整個PIM的體系結構就將如圖2所示。

圖2
圖2

Editable接口定義了一個公共的構造性方法(builder method)getEditor(),它返回一個Editor對象,其完整的代碼如清單1所示。任何一項個人信息都擁有自己獨立的用戶界面(Editor),負責獲取數據並在需要的時候進行修改,而PIM唯一要做事情的只是通過Editable來獲得Editor,並利用它來對用戶輸入的數據進行相應的操作。

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

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

    代碼清單2:editor.py
class Editor:
  """ 用戶使用特定的Editor來編輯個人信息 """
  # 獲取代表用戶界面(UI)的對象
  def getUI(self):
    pass
  
  # 獲取用戶輸入的數據
  def getContent(self):
    pass
  # 提交用戶輸入的數據
  def commitChanges(self):
    pass
    
  # 清空用戶輸入的數據
  def resetUI(self):
    pass

EditableAddress是Editable的一個具體實現,PIM使用它來處理個人地址信息,其完整的代碼如清單3所示。

    代碼清單3:editableaddress.py
from editor import Editor
from editable import Editable
import Tkinter
class EditableAddress(Editable):
  """ 用於處理個人地址信息的Editable """
  
  # 構造函數
  def __init__(self, master):
    self.master = master
    self.name = ""
    self.province = ""
    self.city = ""
    self.street = ""
    self.zipcode = ""
    self.editor = AddressEditor(self)
    
  # 獲取相關聯的Editor
  def getEditor(self):
    return self.editor
  
  
class AddressEditor(Editor, Tkinter.Frame):
  """ 用於處理個人地址信息的Editor """
  
  # 構造函數
  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()
   
  # 構造用戶界面
  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中的方法,獲取用戶輸入的數據
  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中的方法,提交用戶輸入的數據
  def commitChanges(self):
    self.owner.name = self.name.get()
    self.owner.province = self.province.get()
    self.owner.city = self.city.get()
    self.owner.street = self.street.get()
    self.owner.zipcode = self.zipcode.get()
  # 重載Editor中的方法,清空用戶輸入的數據
  def resetUI(self):
    self.name.set("")
    self.province.set("")
    self.city.set("")
    self.street.set("")
    self.zipcode.set("")

EditablePhone是Editable的另一個具體實現,PIM使用它來處理個人電話號碼,其完整的代碼如清單4所示。

    代碼清單4:editablephone.py
from editor import Editor
from editable import Editable
import Tkinter
class EditablePhone(Editable):
  """ 用於處理個人電話號碼的Editable """
  
  # 構造函數
  def __init__(self, master):
    self.master =master
    self.areaCode = "";
    self.phoneNumber = ""
    self.editor = PhoneEditor(self)
    
  # 獲取相關聯的Editor
  def getEditor(self):
    return self.editor
  
class PhoneEditor(Editor, Tkinter.Frame):
  """ 用於處理個人電話號碼的Editor """
  
  # 構造函數
  def __init__(self, owner):
    self.owner = owner
    Tkinter.Frame.__init__(self, self.owner.master)
    self.areaCode = Tkinter.StringVar()
    self.phoneNumber = Tkinter.StringVar()
    # 構造用戶界面
    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中的方法,獲取用戶輸入的數據
  def getContent(self):
    content  = "   Area Code: " + self.areaCode.get() + "\n"
    content += "Phone Number: " + self.phoneNumber.get() + "\n" 
    return content
 
  # 重載Editor中的方法,提交用戶輸入的數據
  def commitChanges(self):
    self.owner.areaCode = self.areaCode.get()
    self.owner.phoneNumber = self.phoneNumber.get()
    
  # 重載Editor中的方法,清空用戶輸入的數據
  def resetUI(self):
    self.areaCode.set("")
    self.phoneNumber.set("")

EditableFactory接口是在PIM中應用工廠方法模式的核心,其完整的代碼如清單5所示。與簡單工廠模式中負責創建所有對象的"超級類"不同,EditableFactory只定義瞭如何實例化Editable的工廠方法createEditable(),並不掌握它們的生殺大權,真正負責完成創建工作的是EditableFactory的子類。

    代碼清單5:editablefactory.py
class EditableFactory:
  """ 用於創建Editable的工廠類 """
  
  #  實例化Editable對象
  def createEditable(self, master):
    pass

EditableAddressFactory是EditableFactory的一個具體實現,PIM使用它來實例化EditableAddress對象,其完整的代碼如清單6所示。

    代碼清單6:editableaddressfactory.py
from editablefactory import EditableFactory
from editableaddress import EditableAddress
class EditableAddressFactory(EditableFactory):
  """ 用於創建EditableAddress的工廠類 """
  
  # 重載EditableFactory中的方法,實例化EditableAddress對象
  def createEditable(self, master):
    address = EditableAddress(master)
    return address

EditablePhoneFactory是EditableFactory的另一個具體實現,PIM使用它來實例化EditablePhone對象,其完整的代碼如清單7所示。

    代碼清單7:editablephonefactory.py
from editablefactory import EditableFactory
from editablephone import EditablePhone
class EditablePhoneFactory(EditableFactory):
  """ 用於創建EditablePhone的工廠類 """
  
  # 重載EditableFactory中的方法,實例化EditablePhone對象
  def createEditable(self, master):
    phone = EditablePhone(master)
    return phone

所有這些輔助類都定義好之後,接下去就可以編寫PIM類了,它提供了一個對各種個人信息進行統一管理的框架,其完整的代碼如清單8所示。

  代碼清單8:pim.py
from editablephone import EditablePhone
from editableaddressfactory import EditableAddressFactory
from editablephonefactory import EditablePhoneFactory
import Tkinter
class PIM:
  """ 個人信息管理 """
  
  # 構造函數
  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()
    
	# 個人信息顯示區域
    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按鈕的回調函數
  def addressClicked(self):
    address = EditableAddressFactory().createEditable(self.editorFrame)
    self.editor.grid_remove()
    self.editor = address.getEditor()
    self.editor.getUI().grid()
  # Phone按鈕的回調函數
  def phoneClicked(self):
    phone = EditablePhoneFactory().createEditable(self.editorFrame)
    self.editor.grid_remove()
    self.editor = phone.getEditor()
    self.editor.getUI().grid()
    
  # Commit按鈕的回調函數
  def commitClicked(self):
    content = self.editor.getContent()
    self.content.set(content)
  
  # Reset按鈕的回調函數
  def resetClicked(self):
    self.editor.resetUI()
    
# 主函數
if (__name__ == "__main__"):
  app = PIM()

圖3是PIM在運行時的界面效果。

圖3
圖3

三、一般結構

工廠方法模式是簡單工廠模式的進一步抽象和推廣,它不僅保持了簡單工廠模式能夠向客戶隱藏類的實例化過程這一優點,而且還通過多態性克服了工廠類過於複雜且不易於擴展的缺點。在工廠方法模式中,處於核心地位的工廠類不再負責所有產品的創建,而是將具體的創建工作交由子類去完成。工廠方法模式中的核心工廠類經過功能抽象之後,成爲了一個抽象的工廠角色,僅負責給出具體工廠子類必須實現的接口,而不涉及哪種產品類應當被實例化這一細節。工廠方法模式的一般性結構如圖4所示,圖中爲了簡化只給出了一個產品類和一個工廠類,但在實際系統中通常需要設計多個產品類和多個工廠類。

圖4
圖4

工廠方法模式的實質是將對象的創建延遲到其子類實現,即由子類根據當前情況動態決定應該實例化哪一個產品類。從上圖可以看出,工廠方法模式涉及到抽象工廠角色、具體工廠角色、抽象產品角色和具體產品角色四個參與者。

  • 抽象工廠(Creator)角色  是工廠方法模式的核心,它負責定義創建抽象產品對象的工廠方法。抽象工廠不能被外界直接調用,但任何在模式中用於創建產品對象的工廠類都必須實現由它所定義的工廠方法。
  • 具體工廠(Concrete Creator)角色  是工廠方法模式的對外接口,它負責實現創建具體產品對象的內部邏輯。具體工廠與應用密切相關,可以被外界直接調用,創建所需要的產品。
  • 抽象產品(Product)角色  是工廠方法模式所創建的所有對象的父類,它負責描述所有具體產品共有的公共接口。
  • 具體產品(Concrete Product)角色  是工廠方法模式的創建目標,所有創建的對象都是充當這一角色的某個具體類的實例。

抽象工廠角色負責聲明工廠方法(factory method),用來"生產"抽象產品,以下是抽象工廠的示例性Python代碼:

    代碼清單9:creator.py
class Creator:
  """ 抽象工廠角色 """
  
  # 創建抽象產品的工廠方法
  def factoryMethod(self):
    pass

具體工廠角色負責創建一個具體產品的實例,並將其返回給調用者。具體工廠是與具體產品相關的,實現時一般常用的做法是爲每個具體產品定義一個具體工廠。以下是具體工廠的示例性Python代碼:

    代碼清單10:concretecreator.py
class ConcreteCreator(Creator):
  """ 具體工廠角色 """
  
  # 創建具體產品的工廠方法
  def factoryMethod(self):
    product =  ConcreteProduct()
    return product

抽象產品角色的主要目的是爲所有的具體產品提供一個共同的接口,通常只需給出相應的聲明就可以了,而不用給出具體的實現。以下是抽象產品類的示例性Python代碼:

    代碼清單11:product.py
class Product:
  """ 抽象產品角色 """
  
  # 所有產品類的公共接口
  def interface(self):
    pass

具體產品角色充當最終的創建目標,一般來講它是抽象產品類的子類,實現了抽象產品類中定義的所有工廠方法,實際應用時通常會具有比較複雜的業務邏輯。以下是具體產品類的示例性Python代碼:

    代碼清單12:concreteproduct.py
class ConcreteProduct(Product):
  """ 具體產品角色 """
  
  # 公共接口的實現
  def interface(self):
    print "Concrete Product Method"

在應用工廠方法模式時,通常還需要再引入一個客戶端角色,由它負責創建具體的工廠對象,然後再調用工廠對象中的工廠方法來創建相應的產品對象。以下是客戶端的示例性Python代碼:

  代碼清單13:client.py
class Client:
  """ 客戶端角色 """
  
  def run(self):
    creator = ConcreteCreator()
    product = creator.factoryMethod()
    product.interface()
# 主函數
if (__name__ == "__main__"):
  client = Client()
  client.run()

在這個簡單的示意性實現裏,充當具體產品和具體工廠角色的類都只有一個,但在真正的實際應用中,通常遇到的都是同時會有多個具體產品類的情況,此時相應地需要提供多個具體工廠類,每個具體工廠都負責生產對應的具體產品。

工廠方法模式的活動序列如圖5所示,客戶端Client首先創建ConcreteCreator對象,然後調用ConcreteCreator對象的工廠方法factoryMethod(),由它負責"生產"出所需要的ConcreteProduct對象。

圖5
圖5

四、實際運用

使用工廠方法模式可以在不修改具體工廠角色的情況下引入新的產品,這一點無疑使得工廠方法模式具有比簡單工廠模式更好的可擴展性。在開發實際的軟件系統時,通常是先設計產品角色,然後纔開始設計工廠角色,而複雜的需求導致將在抽象產品和具體產品之間形成非常龐大的樹狀結構,如圖6所示。

圖6
圖6

在上面的產品等級結構中,出現了多於一個的抽象產品類,以及多於兩個的類層次,這是在構造真實系統中經常遇到的情況。在爲這一軟件體系結構應用工廠方法模式時,通常的做法是按照產品的等級結構再設計一個相同的工廠等級結構,如圖7所示。

圖7
圖7

定義工廠角色的目的是爲了創建相應的產品角色,因此整個系統的架構將如圖8所示。這一結構常常被稱爲平行的類層次(parallel class hierarchies),它使得一個類能夠將它的一些職責委託給另一個獨立的類,而工廠方法則是聯繫兩者之間的紐帶。工廠方法模式並沒有限制產品等級的層數,雖然前面給出的一般性結構中只有兩個層次(抽象產品層和具體產品層),但在實際運用時卻往往需要更加複雜的產品層次。

圖8
圖8

在工廠方法模式的一般性結構中,每當具體工廠類中的工廠方法被請求時,都會調用具體產品類的構造函數來創建一個新的產品實例,然後再將這個實例提供給客戶端。但在實際軟件系統中應用工廠方法模式時,工廠方法所做的事情可能更加複雜,其中最常見到的一種情況是循環使用產品對象。所採用的策略是將工廠對象創建的所有產品對象登記到一個對象池(object pool)中,這樣每當客戶請求工廠方法創建相應的產品對象時,可以先從對象池中查詢符合條件的產品對象,如果對象池中恰巧有這樣的對象,那就直接將這個產品對象返回給客戶端;如果對象池中沒有這樣的對象,那就創建一個新的滿足要求的產品對象,將其登記到對象池中,然後再返回給客戶端。

工廠方法模式依賴於工廠角色和產品角色的多態性,但在實際運用時這個模式可能出現退化,其表現就是多態性的喪失。在工廠方法模式中,所有的具體工廠對象應該共享一個抽象的超類,或者換句話說,應當有多個具體工廠類作爲一個抽象工廠類的子類存在於工廠等級結構中,但如果工廠等級結構中只有一個具體工廠類的話,那麼抽象工廠角色可以省略。當抽象工廠角色被省略時,工廠方法模式就發生了退化,這一退化表現爲工廠角色多態性的喪失,退化後的模式仍然可以發揮部分工廠方法模式的作用,通常被稱爲退化的工廠方法模式。退化的工廠方法模式在很大程度上與簡單工廠模式相似,如圖9所示,實際運用時可以考慮用簡單工廠模式進行替代。

圖9
圖9

在工廠方法模式中,從工廠方法返回的應當是抽象產品類型,而不是具體產品類型,因爲只有這樣才能保證產品角色的多態性。也就是說,調用工廠方法的客戶端可以針對抽象產品類進行編程,而不必依賴於具體產品類。在實際運用時有可能會出現一種很特殊的情況,那就是工廠方法只需要返回一個具體產品類,此時工廠方法模式的功能同樣會發生退化,但這一退化將表現爲產品角色多態性的喪失,如圖10所示。嚴格說來,當工廠方法模式出現這一退化時,就不能再稱爲工廠方法模式了,因爲客戶端從工廠方法的靜態類型就可以判斷出將要得到的是什麼類型的對象,而這一點恰好違背了工廠方法模式的初衷。

圖10
圖10

五、優勢和不足

在工廠方法模式中,工廠方法用來創建客戶所需要的產品,同時還向客戶隱藏了哪種具體產品類將被實例化這一細節。工廠方法模式的核心是一個抽象工廠類,各種具體工廠類通過從抽象工廠類中將工廠方法繼承下來,使得客戶可以只關心抽象產品和抽象工廠,完全不用理會返回的是哪一種具體產品,也不用關心它是如何被具體工廠創建的。

基於工廠角色和產品角色的多態性設計是工廠方法模式的關鍵,它使得工廠可以自主確定創建何種產品對象,而如何創建這個對象的細節則完全封裝在具體工廠內部。工廠方法模式之所以又被稱爲多態工廠模式,顯然是因爲所有的具體工廠類都具有同一抽象父類。

使用工廠方法模式的另一個優點是在系統中加入新產品時,不需要對抽象工廠和抽象產品提供的接口進行修改,而只要添加一個具體工廠和具體產品就可以了,沒有必要修改客戶端,也沒有必須修改其他的具體工廠和具體產品,系統的可擴展性非常好。優秀的面向對象設計鼓勵使用封裝(Encapsulation)和委託(Delegation)來構造軟件系統,而工廠方法模式則是使用了封裝和委託的典型例子,其中封裝是通過抽象工廠來體現的,而委託則是通過抽象工廠將創建對象的責任完全交給具體工廠來體現的。

使用工廠方法模式的缺點是在添加新產品時,需要編寫新的具體產品類,而且還要提供與之對應的具體工廠類,當兩者都比較簡單時,系統的額外開銷相對較大。

六、小結

工廠方法模式的核心思想是定義一個用來創建對象的公共接口,由工廠而不是客戶來決定需要被實例化的類,它通常在構造系統整體框架時被用到。工廠方法模式看上去似乎比較簡單,但是內涵卻極其深刻,抽象、封裝、繼承、委託、多態等面向對象設計中的理論都得到了很好的體現,應用範圍非常廣泛

轉自:http://www.ibm.com/developerworks/cn/linux/l-pypt/part3/index.html

發佈了74 篇原創文章 · 獲贊 26 · 訪問量 25萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章