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.ltesting.net