工厂方法

工厂方法模式英语Factory method pattern)是一种实现了“工厂”概念的面向对象设计模式。就像其他创建型模式一样,它也是处理在不指定对象具体类型的情况下创建对象的问题。工厂方法模式的实质是“定义一个创建对象的接口,但让实现这个接口的类来决定实例化哪个类。工厂方法让类的实例化推迟到子类中进行。”[1]

创建一个对象常常需要复杂的过程,所以不适合包含在一个复合对象中。创建对象可能会导致大量的重复代码,可能会需要复合对象访问不到的信息,也可能提供不了足够级别的抽象,还可能并不是复合对象概念的一部分。工厂方法模式通过定义一个单独的创建对象的方法来解决这些问题。由子类实现这个方法来创建具体类型的对象。

对象创建中的有些过程包括决定创建哪个对象、管理对象的生命周期,以及管理特定对象的创建和销毁的概念。


UML描述的工厂方法模式
LePUS3描述的工厂方法模


工厂
[编辑]

面向对象程序设计中,工厂通常是一个用来创建其他对象的对象。工厂是构造方法抽象,用来实现不同的分配方案。

工厂对象通常包含一个或多个方法,用来创建这个工厂所能创建的各种类型的对象。这些方法可能接收参数,用来指定对象创建的方式,最后返回创建的对象。

有时,特定类型对象的控制过程比简单地创建一个对象更复杂。在这种情况下,工厂对象就派上用场了。工厂对象可能会动态地创建产品对象的类,或者从对象池中返回一个对象,或者对所创建的对象进行复杂的配置,或者应用其他的操作。

这些类型的对象很有用。几个不同的设计模式都应用了工厂的概念,并可以使用在很多语言中。例如,在《设计模式》一书中,像工厂方法模式抽象工厂模式生成器模式,甚至是单例模式都应用了工厂的概念。

代码举例[编辑]

例如,有一个Button类表示按钮,另有它的两个子类WinButtonMacButton分别代表Windows和Mac风格的按钮,那么这几个类和用于创建它们的工厂类在Java中可以如下实现(在此省略所有类和方法的可见性设置):

//几个Button类
class Button{/* ...*/}
class WinButton extends Button{/* ...*/}
class MacButton extends Button{/* ...*/}
 
//它们的工厂类
interface ButtonFactory{
    abstract Button createButton();
}
class WinButtonFactory implements ButtonFactory{
    Button createButton(){
        return new WinButton();
    }
}
class MacButtonFactory implements ButtonFactory{
    Button createButton(){
        return new MacButton();
    }
}

其他举例[编辑]

变种[编辑]

虽然工厂方法模式的背后动机是允许子类选择创建对象的具体类型,但是使用工厂方法模式也有一些其他的好处,其中很多并不依赖于子类。因此,有时候也会创建不使用多态性创建对象的工厂方法,以得到使用工厂方法的其他好处。

工厂“方法”而非工厂“类”[编辑]

如果抛开设计模式的范畴,“工厂方法”这个词也可以指作为“工厂”的方法,这个方法的主要目的就是创建对象,而这个方法不一定在单独的工厂类中。这些方法通常作为静态方法,定义在方法所实例化的类中。

每个工厂方法都有特定的名称。在许多面向对象的编程语言中,构造方法必须和它所在的类具有相同的名称,这样的话,如果有多种创建对象的方式(重载)就可能导致歧义。工厂方法没有这种限制,所以可以具有描述性的名称。举例来说,根据两个实数创建一个复数,而这两个实数表示直角坐标或极坐标,如果使用工厂方法,方法的含义就非常清晰了。当工厂方法起到这种消除歧义的作用时,构造方法常常被设置为私有方法,从而强制客户端代码使用工厂方法创建对象。

下面的例子展示了在不同的编程语言中实现复数创建的代码:

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);

简单工厂[编辑]

普通的工厂方法模式通常伴随着对象的具体类型与工厂具体类型的一一对应,客户端代码根据需要选择合适的具体类型工厂使用。然而,这种选择可能包含复杂的逻辑。这时,可以创建一个单一的工厂类,用以包含这种选择逻辑,根据参数的不同选择实现不同的具体对象。这个工厂类不需要由每个具体产品实现一个自己的具体的工厂类,所以可以将工厂方法设置为静态方法。 而且,工厂方法封装了对象的创建过程。如果创建过程非常复杂(比如依赖于配置文件或用户输入),工厂方法就非常有用了。 比如,一个程序要读取图像文件。程序支持多种图像格式,每种格式都有一个对应的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;
    }
}

适用性[编辑]

下列情况可以考虑使用工厂方法模式:

  • 创建对象需要大量重复的代码。
  • 创建对象需要访问某些信息,而这些信息不应该包含在复合类中。
  • 创建对象的生命周期必须集中管理,以保证在整个程序中具有一致的行为。

工厂方法模式常见于工具包和框架中,在这些库中可能需要创建客户端代码实现的具体类型的对象。

平行的类层次结构中,常常需要一个层次结构中的对象能够根据需要创建另一个层次结构中的对象。

工厂方法模式可以用于测试驱动开发,从而允许将类放在测试中[2]。举例来说,Foo这个类创建了一个Dangerous对象,但是Dangerous对象不能放在自动的单元测试中(可能它需要访问产品数据库,而这个数据库不是随时能够访问到的)。所以,就可以把Dangerous对象的创建交由Foo类的一个方法(虚函数createDangerous完成。为了测试,再创建一个Foo的一个子类TestFoo,重写createDangerous方法,在方法中创建并返回一个FakeDangerousDangerous的子类),而这是一个模拟对象。这样,单元测试就可以使用TestFoo来测试Foo的功能,从而避免了使用Dangerous对象带来的副作用。

局限性[编辑]

使用工厂方法有三个局限,第一个与代码重构有关,另外两个与类的扩展有关。

  • 第一个局限是,重构已经存在的类会破坏客户端代码。例如,Complex类是一个标准的类,客户端使用构造方法将其实例化。可能会有很多这样的客户端代码:
    Complex c = new Complex(-1, 0);
    
    一旦Complex的编写者意识到Complex的实例化应该使用工厂方法实现,他会将Complex的构造方法设为私有。而此时使用它的构造方法的客户端代码就都失效了。
  • 第二个局限是,因为工厂方法所实例化的类具有私有的构造方法,所以这些类就不能扩展了。因为任何子类都必须调用父类的构造方法,但父类的私有构造方法是不能被子类调用的。
  • 第三个局限是,如果确实扩展了工厂方法所实例化的类(例如将构造方法设为保护的,虽然有风险但也是可行的),子类必须具有所有工厂方法的一套实现。例如,在上述Complex的例子中,如果Complex有了一个子类StrangeComplex,那么StrangeComplex必须提供属于它自己的所有工厂方法,否则
    StrangeComplex.fromPolar(1, pi);
    
    将会返回一个Complex(父类)的实例,而不是所希望的子类实例。但有些语言的反射特性可以避免这个问题。

通过修改底层编程语言,使工厂方法称为第一类的类成员,可以缓解这三个问题。[3]

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值