意图
工厂模式时一种创建型模式,其在父类中提供一个创建对象的方法,允许子类决定实例化对象的类型。
问题
假设你正在开发一款物流管理应用。最初版本只能处理卡车运输,因此大部分代码都在位于名为卡车的类中。
一段时间后,这款应用变得受欢迎。你每天都能收到十几次来自海运公司的请求,希望应用能够支持海上物流功能。
tips:如果代码其余部分与现有类已经存在耦合关系,那么想程序中添加新类并没有那么容易。
这可是个好消息。但是代码问题该如何处理呢?目前,大部分代码都与卡车类相关。在程序中添加轮船类需要修改全部代码。更糟糕的是,如果你以后需要在程序中支持另外一种运输方式,很可能需要对这些代码进行大幅修改。
最后,你将不得不编写繁复的代码,根据不同的运输对象类,在应用中进行不同的处理。
解决方案
工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用(即使用new 运算符)。不用担心,对象仍将通过new 运算符创建,只是该运算符改在工厂方法中调用罢了。工厂方法返回的对象通常被称作“产品”。
乍看之下,这种更改可能毫无意义:我们只是改变了程序中调用构造函数的位置而已。但是,仔细想一下,现在你可以在子类中重写工厂方法,从而改变创建产品的类型。
但有一点需要注意:仅当这些产品具有共同的基类或接口时,子类才能返回不同类型的产品,同时基类中的工厂方法还应将其返回类型为这一共有接口。
举例来说,卡车和轮船类都必须实现运输接口,该接口声明了一个名为deliver的方法,每个类都将以不同的方式实现方法,卡车走陆运交付货物,轮船走海运交付货物。陆运类中的工厂方法返回卡车对象,而海运类则返回轮船对象。
tips:只要产品类实现一个共同接口,你就可以将其对象传递给客户代码,而无需提供额外数据。
调用工厂方法的代码无需了解不同子类返回实际对象之间的差别。客户端将所有产品视为抽象的运输。客户端知道所有运输对象都提供交付方法,但是并不关心其具体实现方式。
工厂方法模式适合应用场景
当你在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法
工厂方法将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码
例如,如果需要向应用中添加一种新产品,你只需要开发新的创建者子类,然后重写其工厂方法即可。
如果你希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。
继承可能是扩展软件库或框架行为的最简单的方法。但是当你使用子类替代标准组件时,框架如何辨识出该子类?
解决方案是将个框架中构造组件的代码集中到单个工厂方法中,并在继承组件之外允许任何人对该方法重写。
让我们看看具体是如何实现的。假设你使用开源UI框架编写自己的应用。你希望在应用中使用圆形按钮,但是原框架仅支持矩形按钮。你可以使用圆形按钮子类来继承标准的按钮类。但是,你需要告诉UI框架类使用心得子类按钮代替默认按钮。
为了实现这个功能,你可以根据基础框架类开发子类圆形按钮,并且重写其创建按钮方法。基类中该方法返回按钮对象,而你开发的子类返回圆形对象。现在,你就可以使用圆形按钮UI类代替UI框架类。就是这么简单!
如果你希望复用现在对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。
在处理大型资源密集型对象(比如数据库连接,文件系统和网络资源)时,你会经常碰到这种资源需求。
让我们思考复用现有对象的方法: 1.首先,你需要创建存储空间来存放所有已经创建的对象。
2.当他人请求一个对象时,程序将在对象池中搜索可用对象。
3.。。。然后将其返回给客户端代码。
4.如果没有可用对象,程序则创建一个新对象(并将其添加到对象池中)。
这些代码可不少!而且它们必须位于同一处,这样才能确保重复代码不会污染程序。
可能最显而易见,也是最方便的方式,就是将这些代码放置在我们试图重用的对象类的构造函数中。但是从定义来讲,构造函数始终返回的时新对象,其无法返回现有实例。
因此,你需要有一个既能创建新对象,又可以重用现有对象的普通方法。这听上去和工厂方法非常相像。
实现方式
1.让所有产品都遵循同一接口。该接口必须声明对所有产品都有意义的方法。
2.在创建类中添加一个空的工厂方法。该方法的返回类型必须遵循通用产品接口。
3.在创建者代码中找到对于产品构造函数的所有引用。将它们依次替换为对于工厂方法的调用,同时将创建产品的代码移入工厂方法。你可能需要在工厂方法中添加临时参数来控制返回的产品类型。
工厂方法的代码看上去可能非常糟糕。其中可能会有复杂的switch 分支运算符,用于选择各种需要实例化的产品类,但是不要担心,我们很快就会修复这个问题。
4.现在,为工厂方法中的每种产品编写一个创建者子类,然后在子类中重写工厂方法,闭关将基本方法中的相关创建代码移动到工厂方法中。
5.如果应用中的产品类型太多,那么为每个产品创建子类并无太大必要,这时你也可以在子类中复用基类中的控制参数。
例如,设想你有以下一些层次结构的类。基类邮件及其子类航空邮件和陆运邮件;运输及其子类飞机,卡车和火车。航空邮件仅使用飞机对象,而陆运邮件则会同时使用卡车和火车对象。你可以编写一个新的子类(例如火车邮件)来处理这两种情况,但是还有其他可选方案,客户端代码可以给陆运邮件类传递一个参数,用于控制其希望获得的产品。
6.如果代码经过上述移动后,基础工厂方法中已经没有任何代码,你可以将其装变为抽象类。如果基础工厂方法中还有其他语句,你可以将其设置该方法的默认行为。
工厂方法模式的优缺点
优点:你可以避免创建者和具体产品之间的紧密耦合。
单一职责原则。你可将产品创建放在程序的单一位置,从而是代码更容易维护。
开闭原则。无需更改现有客户端代码,你就可以在程序中引入新的产品类型。
缺点:应用工厂方法模式需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况时将该模式引入创建者类的现有层次结构中。
与其他模式的关系
·在许多设计工作的初期都会使用工厂模式,随后演化为使用抽象狗产模式、原型模式或建造者模式。
·抽象工厂模式通常基于一组工厂方法,但你也可以使用原型模式来生成这些类的方法。
·你可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器,并使得迭代器与集合相匹配。
·原型并不基于继承,因此没有继承的缺点。另一方面,原型需要对被复制对象进行复杂的初始化。工厂方法基于继承,但是它不需要初始化的步骤。
·工厂方法时模板方法模式的一种特殊模式。同时,工厂方法可以作为一个大型模板方法中的一个步骤。
概念示例
using System;
namespace FactoryMethod
{
abstract class Creator
{
public abstract IProduct FatoryMethod();
public string SomeOperation()
{
var product =FactoryMethod();
var result ="Create: The same creator's code has just worked with "+product.Operation();
return result;
}
}
class ConcreteCreator1:Creator
{
public override IProduct FactoryMethod()
{
return new ConcreteProduct1();
}
}
class ConcreteCreator2 : Creator
{
public override IProduct FactoryMethod()
{
return new ConcreteProduct2();
}
}
public interface IProduct
{
return new ConcreteProduct2();
}
class ConcreteProduct1:IProduct
{
public string Operation()
{
return "{Result of ConcreteProduct}";
}
}
}