Python 设计模式系列之二: 创建型 Simple Factory 模式
软件设计大师总是要比初学者更加清楚该如何设计软件,因为他们手中掌握着设计模式这一法宝。作为一种高级的软件复用形式,设计模式是众多优秀软件设计师集体智慧的结晶,能够很好地指导软件设计过程。本系列文章讲述如何在用Python开发软件时应用各种设计模式,此次介绍的是创建型简单工厂(Simple factory)模式。
一、创建型模式
创建型模式(Creational Pattern)对类的实例化过程进行了抽象,能够使软件模块做到与对象创建和组织的无关性。为了使体系结构更加清晰,一些软件在设计上要求当创建类的具体实例时,能够根据具体的语境来动态地决定怎样创建对象,创建哪些对象,以及怎样组织和表示这些对象,而创建型模式所要描述的就是该如何来解决这些问题。
按照生成目标的不同,创建型模式可以分为类的创建型模式和对象的创建型模式两种:
类的创建型模式
类的创建型模式通过使用继承关系,将类的创建交由具体的子类来完成,这样就向外界隐藏了如何得到具体类的实现细节,以及这些类的实例是如何被创建和组织在一起的。
对象的创建型模式
对象的创建型模式通过把对象的创建委托给另一个对象来完成,可以根据语境动态地决定生成哪些具体类的实例,同时还可以向外界隐藏这些实例是如何被创建以及如何被组织在一起的细节。
所有的创建型模式都有两个永恒的主旋律:第一,它们都将系统使用哪些具体类的信息封装起来;第二,它们隐藏了这些类的实例是如何被创建和组织的。外界对于这些对象只知道它们共同的接口,而不清楚其具体的实现细节。正因如此,创建型模式在创建什么(what),由谁(who)来创建,以及何时(when)创建这些方面,都为软件设计者提供了尽可能大的灵活性。
具体到Python来说,假设有这样一个类:
class Person:
def __init__(self, name):
self.name = name
要创建该类的一个实例,则应该执行下面的语句:
p = Person("Gary")
但如果创建对象时完成的工作非常复杂,需要一段很长的代码,你就不能简单地将其全部写入__init__方法中,因为这会违背面向对象思想的两个基本原则:封装(encapsulation)和委派(delegation)。假如执意要做样做,结果只会使你的代码变成一段行为固定的硬编码(hard coding),而整个软件的结构都极有可能变得非常糟糕,因为其它某个模块也许就正依赖于你所创建的这个实例,这样就在无形之间增加了模块之间的耦合度。
将Python对象的创建过程封装到某个类中来单独完成,可以使你的程序变得更加灵活和通用。实践证明,使用下面的六种创建型模式可以更好地改善对象的创建过程:
Simple Factory模式
专门定义一个类来负责创建其它类的实例,被创建的实例通常都具有共同的父类。
Factory Method模式
将对象的创建交由父类中定义的一个标准方法来完成,而不是其构造函数,究竟应该创建何种对象由具体的子类负责决定。
Abstract Factory模式
提供一个共同的接口来创建相互关联的多个对象。
Singleton模式
保证系统只会产生该类的一个实例,同时还负责向外界提供访问该实例的标准方法。
Builder模式
将复杂对象的创建同它们的具体表现形式(representation)区别开来,这样可以根据需要得到具有不同表现形式的对象。
Prototype模式
利用一个能对自身进行复制的类,使得对象的动态创建变得更加容易。
二、模式引入
简单工厂(Simple Factory)模式又称为静态工厂方法(Static Factory Method)模式,属于类的创建型模式。这种模式根据外界给定的信息,由"工厂"对象"制造"出某些可能"产品"类中的一个实例,工厂对象能够处理的所有类通常都继承于同一个父类,并且对外界提供基本相同的接口,只不过在具体实现时会有所差别罢了。
假设我们要开发一个绘图程序,用来绘制简单的几何图形,这个软件应该能够处理下面的几种几何对象:
圆形(Circle)
矩形(Rectangle)
菱形(Diamond)
除了各自特有的属性和方法之外,所有的几何图形几乎都可以抽象出绘制(draw)和擦除(erase)两个公共方法,因而可以为它们定义一个共同的接口Shape。虽然Python语言本身并不支持接口,但为了更好地阐明设计模式的思想,有时我们还是会借用一下UML中的接口这一概念。这样一来,各个类之间的关系就将如图1所示:
Shape接口定义了所有几何图形都必须实现的公共方法: draw()和erase(),实现该接口的Python代码如下所示,Python中没有接口的概念,因此在具体实现时可以使用类来替代。
代码清单1:shape.py
class Shape:
# 绘制图形
def draw(self):
pass
# 擦除图形
def erase(self):
pass
Circle类是Shape的一种具体形式,它实现了Shape接口定义的所有方法,此外还添加了一个属性__radius,用来表示圆的半径。以下是实现Circle类的代码:
代码清单2:circle.py
class Circle (Shape):
def __init__(self, radius = 0):
self.__radius = radius
# 绘制圆形
def draw(self):
print "Draw Circle"
# 擦除圆形
def erase(self):
print "Erase Circle"
# 半径的取值方法
def getRadius(self):
return self.__radius
# 半径的赋值方法
def setRadius(self, radius):
self.__radius = radius
Rectangle类也是Shape的一种具体形式,它实现了Shape接口定义的所有方法,并添加了__width和__height两个属性,分别表示矩形的宽度和高度。以下是实现Rectangle类的代码:
代码清单3:rectangle.py
class Rectangle (Shape):
def __init__(self, width = 0, height = 0):
self.__width = width
self.__height = height
# 绘制矩形
def draw(self):
print "Draw Rectangle"
# 擦除矩形
def erase(self):
print "Erase Rectangle"
# 宽度的取值方法
def getWidth(self):
return self.__width
# 宽度的赋值方法
def setWidth(self, width):
self.__width = width
# 高度的取值方法
def getHeight(self):
return self.__height
# 高度的赋值方法
def setHeight(self, height):
self.__height = height
同样,Diamond类也是Shape的一种具体形式,它实现了Shape接口中定义的所有方法,并且添加了__width和__height两个属性,分别表示菱形的宽度和高度。以下是实现Diamond类的代码:
代码清单4:diamond.py
class Diamond (Shape):
def __init__(self, width = 0, height = 0):
self.__width = width
self.__height = height
# 绘制菱形
def draw(self):
print "Draw Diamond"
# 擦除菱形
def erase(self):
print "Erase Diamond"
# 宽度的取值方法
def getWidth(self):
return self.__width
# 宽度的赋值方法
def setWidth(self, width):
self.__width = width
# 高度的取值方法
def getHeight(self):
return self.__height
# 高度的赋值方法
def setHeight(self, height):
self.__height = height
所有几何图形类都定义好后,下面要做的就是提供一个"工厂"类ShapeFactory,来创建各种几何图形的具体实例。ShapeFactory类的作用就是根据外界的要求,创建出不同的几何图形对象,如圆形(Circle)、矩形(Rectangle)或菱形(Diamond),这样整个软件的体系结构就将如图2所示。
ShapeFactory类用于创建各种几何图形的实例,其实现代码如下所示:
代码清单5:shapefactory.py
class ShapeFactory:
def factory(self, which):
if which == "Circle":
return Circle()
elif which == "Rectangle":
return Rectangle()
elif which == "Diamond":
return Diamond()
else:
return None
在ShapeFactory类中只定义了一个方法factory(),外界通过调用该方法,来创建其所需的几何图形对象,但如果所请求的类是系统所不支持的,则将返回None。在引入了工厂类之后,其它模块如果想生成几何图形类的实例,只需调用ShapeFactory类的factory()方法就可以了:
fac = ShapeFactory()
shape = fac.factory("Diamond")
if shape != None:
shape.draw()
就样就成功地将类是如何创建的这一实现细节向外界隐藏起来了,这就是简单工厂模式所采取的基本策略。
三、一般结构
简单工厂模式属于类的创建型模式,适合用来对大量具有共同接口的类进行实例化,它可以推迟到运行的时候才动态决定要创建哪个类的实例,而不是在编译时就必须知道要实例化哪个类。简单工厂模式的一般性结构如图3所示:
简单工厂模式的实质是由一个工厂类根据传入的参量,动态决定应该创建出哪一个产品类的实例。从上图可以看出,简单工厂模式涉及到工厂角色、抽象产品角色和具体产品角色三个参与者。
工厂(Creator)角色
是简单工厂模式的核心,它负责实现创建所有实例的内部逻辑。工厂类可以被外界直接调用,创建所需的产品对象。
抽象产品(Product)角色
是简单工厂模式所创建的所有对象的父类,它负责描述所有实例所共有的公共接口。
具体产品(Concrete Product)角色
是简单工厂模式的创建目标,所有创建的对象都是充当这个角色的某个具体类的实例。
工厂角色负责创建一个具体产品的实例,并将其返回给调用者,以下是工厂类的示例性Python代码:
代码清单6:creator.py
class Creator:
# 创建具体产品类的方法
def factory(self):
return ConcreteProduct()
抽象产品角色的主要目的是为所有的具体产品提供一个共同的接口,通常只需给出相应的声明就可以了,而不用给出具体的实现。以下是抽象产品类的示例性Python代码:
代码清单7:product.py
class Product:
# 所有具体产品类的公共接口
def interface(self):
pass
具体产品角色充当最终的创建目标,一般来讲它是抽象产品类的子类,实现了抽象产品类中定义的所有接口方法。以下是具体产品类的示例性Python代码:
代码清单8:concrete.py
class ConcreteProduct(Product):
# 公共接口的实现
def interface(self):
print "Concrete Product Method"
在应用简单工厂模式时,可以采用下面的示例性Python代码:
fac = Creator()
p = fac.factory()
if p != None:
p.interface()
在这个简单的示意性实现里,充当具体产品角色的类只有一个,但在真正的实际应用中,通常遇到的都是同时有多个具体产品类的情况。
四、实际运用
在运用简单工厂模式开发真正的软件系统时,抽象产品和具体产品之间可能会形成非常复杂的树状结构,如图4所示。
虽然这样的结构并不符合所有具体产品类都继承于同一抽象产品类的基本原则,但也不难发现其实所有的具体产品类归根结底都源自于同一个抽象产口类,遇到这种情况时只需稍加变通,依然能够使用同一个工厂类来创建所有具体产品类的实例,只不过此时的软件结构将如图5所示。
当抽象产品类和具体产品类的关系变得复杂起来时,使用简单工厂模式的一个好处就是各种产品类之间的结构关系不会反映到工厂类中,并且它们结构上的变化也不会影响到工厂类。正是因为简单工厂模式对各种具体产品类者是单独对待的,因此工厂类并不需要弄清楚它们之间的结构关系,而只需知道究竟有哪些具体产品类可以被实例化以及如何实例化就可以了。不过这样做有时也会产生一些麻烦,那就是在增加新的具体产品类时,不可避免地会导致对工厂类的修改。
如果在实际中运用简单工厂模式时遇到只有一个具体产品类的情况,为了简化软件体系结构,可以考虑对简单工厂模式进行精简,省略抽象产品这一角色。此时简单工厂模式的结构将如图6所示。
在简单厂模式中,充当工厂角色的类通常只会有一个方法,那就是用来创建具体产品对象的factory()。在某些特殊场合下,如果需要的话也可以考虑将工厂角色的功能交由抽象产品角色来替代完成,这时简单工厂模式的结构将如图7所示。
在更极端的情况下,工厂角色、抽象产品角色和具体产品角色三者可以合并,即将所有的功能都交由具体产品类来统一完成。也就是说,简单工厂模式将退化成一个单独的具体产品类,这个类同时也是自身的工厂,负责创建自己的实例,如图8所示。
这种退化的简单工厂模式从实质上讲等价于一个类,唯一不同的是在对象初始化时不再使用默认的构造方法,而是使用自定义的工厂方法,但这在许多场合下的意义并不是很大,尤其是对Python这种动态类型语言来说更是如此。由于退化后的模式并没有充分利用简单工厂模式的特点,所以最好慎重使用,当然也可以考虑用其它模式进行替代。
五、优势和不足
在简单工厂模式中,工厂类是整个模式的关键所在,它包含必要的判断逻辑,能够根据外界给定的信息,决定究竟应该创建哪个具体类的对象。通过使用工厂类,外界可以从直接创建具体产品对象的尴尬局面中摆脱出来,仅仅只需要负责"消费"对象就可以了,而不必管这些对象究竟是如何创建以及如何组织的,这样就清晰了各自的职责和权力,有利于整个软件体系结构的优化。
不过,凡事有利就有弊,简单工厂模式的缺点也正体现在其工厂类上。由于工厂类集中了所有实例的创建逻辑,是一个无所不能的"全能类"(God Class),因此它对整个软件系统是至关重要的,如果这个类无法正常工作,其它部分可能都会受到牵连,正所谓"城门失火,殃及池鱼"。
将全部创建逻辑都集中到一个工厂类中还有另外一个缺点,那就是当系统中的具体产品类不断增多时,可能会出现要求工厂类根据不同条件创建不同实例的需求,这种对条件的判断和对具体产品类型的判断交织在一起,很难避免模块功能的蔓延,对系统的扩展和维护也非常不利。
简单工厂模式的这些缺点,在下次将要介绍的工厂方法(Factory Method)模式中得到了一定程度上的克服,好了,下次再见吧!
六、小结
创建型模式的目标是做到对象创建和组织的无关性,它可以细分为类的创建型模式和对象的创建型模式。简单工厂模式属于类的创建型模式,它可以根据外界所给定的信息,用工厂对象制造出某种特定产品类的实例。完整的简单工厂模式包含工厂、抽象产品和具体产品三个角色,但在实际运用时可以灵活掌握,对模型进行简单化。简单工厂模式的优点是它的工厂类能够负责所有对象的创建,而缺点则是工厂类的实现逻辑可能过于复杂。
文章出处:DIY部落(http://www.diybl.com/course/1_web/webjs/2008515/116539.html)