Python 設計模式——Factory Method模式
發表于:2007-07-04來源:作者:點擊數:
標簽:
一、簡介 工廠方法(Factory Method)模式又稱為虛擬構造器(Virtual Constructor)模式或者多態工廠(Polymorphic Factory)模式,屬于類的創建型模式。在工廠方法模式中,父類負責定義創建對象的公共接口,而子類則負責生成具體的對象,這樣做的目的是將類
一、簡介
工廠方法(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 簡單說來,工廠方法模式的作用就是可以根據不同的條件生成各種類的實例,這些實例通常屬于多個相似的類型,并且具有共同的父類。工廠方法模式將這些實例的創建過程封裝了起來,從而簡化了客戶程序的編寫,并改善了軟件體系結構的可擴展性,使得將來能夠以最小的代價加入新的子類。工廠方法這一模式適合在如下場合中運用:
當無法得知必須創建的對象屬于哪個類的時候,或者無法得知屬于哪個類的對象將被返回的時候,但前提是這些對象都符合一定的接口標準。
當一個類希望由它的子類來決定所創建的對象的時候,其目的是使程序的可擴展性更好,在加入其他類時更具彈性。
當創建對象的職責被委托給多個幫助子類(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 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 cre
原文轉自:http://www.kjueaiud.com