工厂 (面向对象编程)
在面向对象程序设计中,工厂通常是一个用来创建其他对象的对象。工厂是构造方法的抽象,用来实现不同的分配方案。
工厂对象通常包含一个或多个方法,用来创建这个工厂所能创建的各种类型的对象。这些方法可能接收参数,用来指定对象创建的方式,最后返回创建的对象。
有时,特定类型对象的控制过程比简单地创建一个对象更复杂。在这种情况下,工厂对象就派上用场了。工厂对象可能会动态地创建产品类的对象,或者从对象池中返回一个对象,或者对所创建的对象进行复杂的配置,或者应用其他的操作。
这些类型的对象很有用。几个不同的设计模式都应用了工厂的概念,并可以使用在很多语言中。例如,在《设计模式》一书中,像工厂方法模式、抽象工厂模式、建造者模式,甚至是单例模式都应用了工厂的概念。
简单工厂
[编辑]普通的工厂方法模式通常伴随着对象的具体类型与工厂具体类型的一一对应,客户端代码根据需要选择合适的具体类型工厂使用。然而,这种选择可能包含复杂的逻辑。这时,可以创建一个单一的工厂类,用以包含这种选择逻辑,根据参数的不同选择实现不同的具体对象。
这个工厂类不需要由每个具体产品实现一个自己的具体的工厂类,所以可以将工厂方法设置为静态方法。而且,工厂方法封装了对象的创建过程。如果创建过程非常复杂(比如依赖于配置文件或用户输入),工厂方法就非常有用了。比如,一个程序要读取图像文件。程序支持多种图像格式,每种格式都有一个对应的ImageReader
类用来读取图像。程序每次读取图像时,需要基于文件信息创建合适类型的ImageReader
。这个选择逻辑可以包装在一个简单工厂中:
public class ImageReaderFactory {
public static ImageReader imageReaderFactoryMethod(InputStream is) {
ImageReader product = null;
int imageType = determineImageType(is);
switch (imageType) {
case ImageReaderFactory.GIF:
product = new GifReader(is);
case ImageReaderFactory.JPEG:
product = new JpegReader(is);
//...
}
return product;
}
}
工厂方法
[编辑]虽然工厂方法模式的背后动机是允许子类选择创建对象的具体类型,但是使用工厂方法模式也有一些其他的好处,其中很多并不依赖于子类。因此,有时候也会创建不使用多态性创建对象的工厂方法,以得到使用工厂方法的其他好处。
如果抛开设计模式的范畴,“工厂方法”这个词也可以指作为“工厂”的方法,这个方法的主要目的就是创建对象,而这个方法不一定在单独的工厂类中。这些方法通常作为静态方法,定义在方法所实例化的类中。
每个工厂方法都有特定的名称。在许多面向对象的编程语言中,构造方法必须和它所在的类具有相同的名称,这样的话,如果有多种创建对象的方式(重载)就可能导致歧义。工厂方法没有这种限制,所以可以具有描述性的名称。举例来说,根据两个实数创建一个复数,而这两个实数表示直角坐标或极坐标,如果使用工厂方法,方法的含义就非常清晰了。当工厂方法起到这种消除歧义的作用时,构造方法常常被设置为私有方法,从而强制客户端代码使用工厂方法创建对象。
示例
[编辑]下面的例子展示了在不同的编程语言中实现复数创建的代码:
Java
[编辑]class Complex {
public static Complex fromCartesianFactory(double real, double imaginary) {
return new Complex(real, imaginary);
}
public static Complex fromPolarFactory(double modulus, double angle) {
return new Complex(modulus * cos(angle), modulus * sin(angle));
}
private Complex(double a, double b) {
//...
}
}
Complex product = Complex.fromPolarFactory(1, pi);
VB.NET
[编辑]Public Class Complex
Public Shared Function fromCartesianFactory(real As Double, imaginary As Double) As Complex
Return (New Complex(real, imaginary))
End Function
Public Shared Function fromPolarFactory(modulus As Double, angle As Double) As Complex
Return (New Complex(modulus * Math.Cos(angle), modulus * Math.Sin(angle)))
End Function
Private Sub New(a As Double, b As Double)
'...
End Sub
End Class
Complex product = Complex.fromPolarFactory(1, pi);
C#
[编辑]public class Complex {
public double real;
public double imaginary;
public static Complex fromCartesianFactory(double real, double imaginary) {
return new Complex(real, imaginary);
}
public static Complex fromPolarFactory(double modulus , double angle) {
return new Complex(modulus * Math.Cos(angle), modulus * Math.Sin(angle));
}
private Complex (double a, double b) {
real = a;
imaginary = b;
}
}
Complex product = Complex.fromPolarFactory(1,pi);
局限性
[编辑]使用工厂方法有三个局限,第一个与代码重构有关,另外两个与类的扩展有关。
- 第一个局限是,重构已经存在的类会破坏客户端代码。例如,
Complex
类是一个标准的类,客户端使用构造方法将其实例化。可能会有很多这样的客户端代码:一旦Complex的编写者意识到Complex c = new Complex(-1, 0);
Complex
的实例化应该使用工厂方法实现,他会将Complex
的构造方法设为私有。而此时使用它的构造方法的客户端代码就都失效了。
- 第二个局限是,因为工厂方法所实例化的类具有私有的构造方法,所以这些类就不能扩展了。因为任何子类都必须调用父类的构造方法,但父类的私有构造方法是不能被子类调用的。
- 第三个局限是,如果确实扩展了工厂方法所实例化的类(例如将构造方法设为保护的,虽然有风险但也是可行的),子类必须具有所有工厂方法的一套实现。例如,在上述
Complex
的例子中,如果Complex
有了一个子类StrangeComplex
,那么StrangeComplex
必须提供属于它自己的所有工厂方法,否则将会返回一个StrangeComplex.fromPolar(1, pi);
Complex
(父类)的实例,而不是所希望的子类实例。但有些语言的反射特性可以避免这个问题。
通过修改底层编程语言,使工厂方法称为第一类的类成员,可以缓解这三个问题。[1]
引用
[编辑]- ^ Agerbo, Aino; Agerbo, Cornils. How to preserve the benefits of design patterns. Conference on Object Oriented Programming Systems Languages and Applications (Vancouver, British Columbia, Canada: ACM). 1998: 134–143. ISBN 1-58113-005-8.
- Eric, Freeman; Robson, Elisabeth; Bates, Bert; Sierra, Kathy. Head First Design Patterns. Head First. O'Reilly. 2009 [2004]. ISBN 978-0-596-55656-3.