工厂方法
工厂
在面向对象程序设计中,工厂通常是一个用来创建其他对象的对象。工厂是构造方法的抽象,用来实现不同的分配方案。
工厂对象通常包含一个或多个方法,用来创建这个工厂所能创建的各种类型的对象。这些方法可能接收参数,用来指定对象创建的方式,最后返回创建的对象。
有时,特定类型对象的控制过程比简单地创建一个对象更复杂。在这种情况下,工厂对象就派上用场了。工厂对象可能会动态地创建产品对象的类,或者从对象池中返回一个对象,或者对所创建的对象进行复杂的配置,或者应用其他的操作。
这些类型的对象很有用。几个不同的设计模式都应用了工厂的概念,并可以使用在很多语言中。例如,在《设计模式》一书中,像工厂方法模式、抽象工厂模式、生成器模式,甚至是单例模式都应用了工厂的概念。
代码举例
例如,有一个Button类表示按钮,另有它的两个子类WinButton和MacButton分别代表Windows和Mac风格的按钮,那么这几个类和用于创建它们的工厂类在Java中可以如下实现(在此省略所有类和方法的可见性设置):
//几个Button类classButton{/* ...*/}classWinButtonextendsButton{/* ...*/}classMacButtonextendsButton{/* ...*/}//它们的工厂类interfaceButtonFactory{abstractButtoncreateButton();}classWinButtonFactoryimplementsButtonFactory{ButtoncreateButton(){returnnewWinButton();}}classMacButtonFactoryimplementsButtonFactory{ButtoncreateButton(){returnnewMacButton();}}
其他举例
在ADO.NET中,IDbCommand.CreateParameter使用工厂方法连接平行的类层次结构。
在Qt,QMainWindow::createPopupMenu是在框架中定义的工厂方法,可以在应用代码中重写。
在Java中,javax.xml.parsers包使用了几个工厂方法。例如javax.xml.parsers.DocumentBuilderFactory和javax.xml.parsers.SAXParserFactory。
变种
虽然工厂方法模式的背后动机是允许子类选择创建对象的具体类型,但是使用工厂方法模式也有一些其他的好处,其中很多并不依赖于子类。因此,有时候也会创建不使用多态性创建对象的工厂方法,以得到使用工厂方法的其他好处。
工厂“方法”而非工厂“类”
如果抛开设计模式的范畴,“工厂方法”这个词也可以指作为“工厂”的方法,这个方法的主要目的就是创建对象,而这个方法不一定在单独的工厂类中。这些方法通常作为静态方法,定义在方法所实例化的类中。
每个工厂方法都有特定的名称。在许多面向对象的编程语言中,构造方法必须和它所在的类具有相同的名称,这样的话,如果有多种创建对象的方式(重载)就可能导致歧义。工厂方法没有这种限制,所以可以具有描述性的名称。举例来说,根据两个实数创建一个复数,而这两个实数表示直角坐标或极坐标,如果使用工厂方法,方法的含义就非常清晰了。当工厂方法起到这种消除歧义的作用时,构造方法常常被设置为私有方法,从而强制客户端代码使用工厂方法创建对象。
下面的例子展示了在不同的编程语言中实现复数创建的代码:
Java
classComplex{publicstaticComplexfromCartesianFactory(doublereal,doubleimaginary){returnnewComplex(real,imaginary);}publicstaticComplexfromPolarFactory(doublemodulus,doubleangle){returnnewComplex(modulus*cos(angle),modulus*sin(angle));}privateComplex(doublea,doubleb){//...}}Complexproduct=Complex.fromPolarFactory(1,pi);
VB.NET
PublicClassComplexPublicSharedFunctionfromCartesianFactory(realAsDouble,imaginaryAsDouble)AsComplexReturn(NewComplex(real,imaginary))EndFunctionPublicSharedFunctionfromPolarFactory(modulusAsDouble,angleAsDouble)AsComplexReturn(NewComplex(modulus*Math.Cos(angle),modulus*Math.Sin(angle)))EndFunctionPrivateSubNew(aAsDouble,bAsDouble)"...EndSubEndClassComplexproduct=Complex.fromPolarFactory(1,pi);
C#
publicclassComplex{publicdoublereal;publicdoubleimaginary;publicstaticComplexfromCartesianFactory(doublereal,doubleimaginary){returnnewComplex(real,imaginary);}publicstaticComplexfromPolarFactory(doublemodulus,doubleangle){returnnewComplex(modulus*Math.Cos(angle),modulus*Math.Sin(angle));}privateComplex(doublea,doubleb){real=a;imaginary=b;}}Complexproduct=Complex.fromPolarFactory(1,pi);
简单工厂
普通的工厂方法模式通常伴随着对象的具体类型与工厂具体类型的一一对应,客户端代码根据需要选择合适的具体类型工厂使用。然而,这种选择可能包含复杂的逻辑。这时,可以创建一个单一的工厂类,用以包含这种选择逻辑,根据参数的不同选择实现不同的具体对象。这个工厂类不需要由每个具体产品实现一个自己的具体的工厂类,所以可以将工厂方法设置为静态方法。 而且,工厂方法封装了对象的创建过程。如果创建过程非常复杂(比如依赖于配置文件或用户输入),工厂方法就非常有用了。 比如,一个程序要读取图像文件。程序支持多种图像格式,每种格式都有一个对应的ImageReader类用来读取图像。程序每次读取图像时,需要基于文件信息创建合适类型的ImageReader。这个选择逻辑可以包装在一个简单工厂中:
publicclassImageReaderFactory{publicstaticImageReaderimageReaderFactoryMethod(InputStreamis){ImageReaderproduct=null;intimageType=determineImageType(is);switch(imageType){caseImageReaderFactory.GIF:product=newGifReader(is);caseImageReaderFactory.JPEG:product=newJpegReader(is);//...}returnproduct;}}
适用性
下列情况可以考虑使用工厂方法模式:
创建对象需要大量重复的代码。
创建对象需要访问某些信息,而这些信息不应该包含在复合类中。
创建对象的生命周期必须集中管理,以保证在整个程序中具有一致的行为。
工厂方法模式常见于工具包和框架中,在这些库中可能需要创建客户端代码实现的具体类型的对象。
平行的类层次结构中,常常需要一个层次结构中的对象能够根据需要创建另一个层次结构中的对象。
工厂方法模式可以用于测试驱动开发,从而允许将类放在测试中。举例来说,Foo这个类创建了一个Dangerous对象,但是Dangerous对象不能放在自动的单元测试中(可能它需要访问产品数据库,而这个数据库不是随时能够访问到的)。所以,就可以把Dangerous对象的创建交由Foo类的一个方法(虚函数)createDangerous完成。为了测试,再创建一个Foo的一个子类TestFoo,重写createDangerous方法,在方法中创建并返回一个FakeDangerous(Dangerous的子类),而这是一个模拟对象。这样,单元测试就可以使用TestFoo来测试Foo的功能,从而避免了使用Dangerous对象带来的副作用。
局限性
使用工厂方法有三个局限,第一个与代码重构有关,另外两个与类的扩展有关。
第一个局限是,重构已经存在的类会破坏客户端代码。例如,Complex类是一个标准的类,客户端使用构造方法将其实例化。可能会有很多这样的客户端代码: Complexc=newComplex(-1,0); 一旦Complex的编写者意识到Complex的实例化应该使用工厂方法实现,他会将Complex的构造方法设为私有。而此时使用它的构造方法的客户端代码就都失效了。
第二个局限是,因为工厂方法所实例化的类具有私有的构造方法,所以这些类就不能扩展了。因为任何子类都必须调用父类的构造方法,但父类的私有构造方法是不能被子类调用的。
第三个局限是,如果确实扩展了工厂方法所实例化的类(例如将构造方法设为保护的,虽然有风险但也是可行的),子类必须具有所有工厂方法的一套实现。例如,在上述Complex的例子中,如果Complex有了一个子类StrangeComplex,那么StrangeComplex必须提供属于它自己的所有工厂方法,否则 StrangeComplex.fromPolar(1,pi); 将会返回一个Complex(父类)的实例,而不是所希望的子类实例。但有些语言的反射特性可以避免这个问题。
通过修改底层编程语言,使工厂方法称为第一类的类成员,可以缓解这三个问题。
参考文献
Fowler, Martin; Kent Beck, John Brant, William Opdyke, and Don Roberts. Refactoring: Improving the Design of Existing Code. Addison-Wesley. June 1999. ISBN 0-201-48567-2.
Gamma, Erich; Helm, Richard; Johnson, Ralph; Vlissides, John. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley. 1994. ISBN 0-201-63361-2.
Cox, Brad J.;. Object-oriented programming: an evolutionary approach. Addison-Wesley. 1986. ISBN 978-0-201-10393-9.
Cohen, Tal; Gil, Joseph.Better Construction with Factories(PDF). Journal of Object Technology (Bertrand Meyer). 2007 [2007-03-12].
参见
设计模式 (计算机)
抽象工厂
生成器模式
模板方法
免责声明:以上内容版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。感谢每一位辛勤著写的作者,感谢每一位的分享。
- 有价值
- 一般般
- 没价值