0%

译-设计模式-创建模式之Factory Method

目的

Factory Method是创建模式的一种,他在父类中提供一个用来创建对象的接口,但是允许子类修改创建对象的类型。

问题

假设你有一个后勤管理应用。应用的第一个版本只需要处理卡车这种运输方式,所以,你有一个Truck类。

不久,你的应用很受欢迎,你收到了许多需求,包括水运。

problem.png

好消息,不是吗?但是你的代码怎么样呢?看起来,你的代码有许多地方要和Truck类耦合。添加Ship类需要改变整个代码库。此外,如果你决定添加另外一个类型的配送方式,你可能需要再次修改整个代码库。

解决

Factory Method模式建议通过调用一个特别的“工厂”方法来代替直接创建对象(使用new操作)。构造方法应该移到那个方法中调用。工厂方法返回的对象被称为“产品”。

乍一看,这么做似乎没有意义。但是,现在你可以在字类中重写工厂方法,并且改变要创建对象的类。我们来看它是怎么工作的:

solution.png

当然,这种方式有个限制:所有的产品必须遵循一个通用接口(Transport)。基类中的工厂方法需要返回这个通用接口。

solution1.png

字类返回不同的具体产品,它们都有一样的基类或者接口(比如:Truck和Ship都实现了Transport接口)。

solution3.png

工厂方法的客户端不需要关心它得到产品的类型。它们通过产品接口和所有产品协作。

结构

structure.png

  1. Product为creator和他的字类能够生产的对象定义接口。

  2. Concrete Product是Product的不同实现。Concrete Creator将会创建并返回这饿类的实例。

  3. Creator声明了一个返回Product类型的工厂方法。在第一个例子中,所有的Concrete Creator必须实现它们的工厂方法。

    尽管这个名字在现实世界中,生产产品并不是Concrete Creator的主要责任。通常,他有一些处理产品的核心逻辑。

    打个比方:大的软件开发公司有对开发者进行培训的部门。但是公司的主要功能还是写代码。

  4. Concrete Creator实现或者重写基本的工厂方法,创建并返回一个Concrete Product。

    注意:工厂方法并不总是创建一个新实例。它也可以从缓存中返回一个已经存在的对象。

伪代码

这个例子用来展示工厂方法可以被用来创建跨平台的UI元素。工厂放被声明在对话框UI的基类中。它返回抽象的按钮。对话框的积累重写工厂方法并返回特定的按钮。

pseudocode.png

结果是使用基本对话框的代码组合了一个UI窗口。对话框通过通用接口和那妞协作。所以,不管工厂方法返回什么类型的按钮,对话框保留基本的功能。

因此,Factory Method使得类的主要代码独立于所使用的具体产品类。Factory Method让字类担负起选择生产产品所需要的具体类的责任。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
// The Factory Method pattern is applicable only when there is a
// products hierarchy.
interface Button is
method render()
method onClick(f)

class WindowsButton implements Button is
method render(a, b) is
Create and render a Windows looking button.
method onClick(f) is
Bind a native OS click event.

class HTMLButton implements Button is
method render(a, b) is
Return an HTML representation of a button.
method onClick(f) is
Bind a web browser click event.

// Base factory class. Note that the "factory" is merely a role for the class.
// It should have some core business logic which needs different products to
// be created.
class Dialog is
method renderWindow() is
Render other window controls.

Button okButton = createButton();
okButton.onClick(closeDialog);
okButton.render();

// Therefore we extract all product creation code to a special
// factory method.
abstract method createButton()

// Concrete factories extend that method to produce different kinds of products.
class WindowsDialog extends Dialog is
method createButton() is
return new WindowsButton()

class WebDialog extends Dialog is
method createButton() is
return new HTMLButton()

class ClientApplication is
field dialog: Dialog

// Application picks a factory type depending on configuration
// or environment.
method configure() is
if (we are in windows environment) then
dialog = new WindowsDialog()

if (we are in web environment) then
dialog = new WebDialog()

// The client code should work with factories and products through their
// abstract interfaces. This way it will remain functional even if you add
// new product types to the program.
method main() is
dialog.initialize()
dialog.render()

适用性

  • 当你的代码需要和不知道具体类型及一来对象协作时。

    比如,从多数据源读写数据:文件系统,数据库或者网络。这些资源有着不同的类型,依赖和初始化代码。

    工厂方法对其他代码隐藏了产品的实现细节。支持一个新的产品类型,你只需要创建一个新的字类并且重写工厂方法。

  • 当你想要让用户扩展你的类库或者框架的内部组件时。

    用户可以轻松的创建特定组件的字类。但是如何让框架识别这个字类并且替换标准的组件?用户必须重写每一个创建标准组件的方法,把它们改成创建自定义子类的对象。这样相当尴尬,不是吗?

    最好的方法不仅仅是给用户提供扩展某个类,而是将生成组件的代码减少到单个创建方法中。换句话说,提供出一个工厂方法。

    让我们看看它如何工作。假设你在用一个开源的UI框架写app,你的app必须使用圆边按钮,但是框架只提供了一个方形的。

    你做的第一件事就是实现一个RoundButton类。但是现在你需要告诉UIFramework类用一个新的按钮类代替默认的那个。

    为了实现这个,你创建了基础框架的一个子类UIWithRoundButtons,并且重写了createButton方法。这个方法会返回一个Button对象,除了你创建的新子类RoundButton对象。现在,在你的app中,你必须使用UIWithRoundButtons类替代UIFramework来初始化框架。

  • 当你想要保存系统资源并且重用存在的对象,而不是重新建立一个时。

    比如,当你需要处理像数据库连接这类大的或者资源紧张对象。

    想象下要重用存在的对象需要做什么:

    1. 首先,你需要创建一个池子来保存存在的对象。

    2. 当有人请求一个对象时,你需要在池子中找到一个闲置的对象。

    3. 然后把它返回给客户端代码。

    4. 仅仅在池子中没有闲置对象时,你才需要创建一个新的(并且把它放到池子中)。

      这些代码必须放在某个地方。最便利的地方是构造方法。这样子就可以在请求创建对象时去做那些检查。但是,从定义上讲构造方法必须返回新对象,所以它们不能返回存在的实例。

      另外,那些使用你对象的客户端代码也不能包含它。否则,客户端就知道太多你的类中的实现细节。因此,你需要一个独立的方法封装这个逻辑。那就是工厂方法。

如何实现

  1. 为所有产品抽离出通用的接口。这个接口应该声明对每个产品都有意义的方法。

  2. 在创建类中添加一个空的工厂方法。他的签名应当返回产品的接口类型。

  3. 查看创建者的代码并且找到所有用到产品构造方法的引用。将它们逐一替换为对工厂方法的调用,并将产品创建代码提取到工厂方法。
    你可能需要为工厂方法添加临时变量来控制要创建的产品。此时,工厂方法的代码看起来很挫。
    它有一个巨大的switch操作来获取要实例化产品的类。不过不用担心,我们马上修复他。

  4. 现在,在子类中重写工厂方法,并且把父类中对应的case语句挪到子类中。

  5. 基础创建类中的控制参数也可以用在子类中。

    比如,你可能有一个Mail作为基类的层级结构,里面有Air和Ground,还有产品类:Plane,Truck和Train。Air正好和Plane匹配,但是Ground可以同时和Truck还有Train匹配。你可以创建一个新的子类来处理这两种情况,但是还有一种处理方式。客户端代码向Ground的工厂方法传递一个参数来控制它想要的产品。

  6. 如果基本工厂方法在移动后变成空的了,你可以把它标志成抽象方法。

优点

  • 遵循开闭原则。

  • 避免产品和使用它们代码的耦合。

  • 把创建代码都移动到一个地方,简化了代码。

  • 简化添加新产品的编程。

缺点

  • 需要额外的子类。

和其他模式的关系

  • 通常,设计从使用Factory Method开始(比较简单,并且可以通过子类实现定制),逐渐演变到Abstract Factory,Prototype,或者Builder(更加复杂,但更灵活),因为设计者发现它们需要更灵活的程序。

  • Abstract Factory类通常使用工厂方法实现,但也可以使用Prototype实现。

  • Factory Method可以和Iterator单独使用,让集合子类返回正确的迭代器。

  • Prototype不需要子类,但是需要一个“初始”操作。Factory Method需要子类,但是不需要初始化这个步骤。

  • Factory Method是一个专业化的Template Method。另一方面,Factory Method常常作为一个大Template Method的一个步骤。

小结

工厂方法是一个用来解决创建产品对象时没有指定具体类的问题的设计模式。

工厂方法定义了一个方法,用来替代直接调用构造方法(new操作)。子类可以重写这个方法来改变要创建对象的类。

If you can’t figure out the difference between Factories, Factory Method & Abstract Factory patterns, then read our Factory Comparison guide.
如果你分不清楚Factories,Factory Method和Abstract Factory,可以阅读工厂比较指南(待译)

在Java中的使用

  • 用例

    java.util.Calendar#getInstance()

    java.util.ResourceBundle#getBundle()

    java.text.NumberFormat#getInstance()

    java.nio.charset.Charset#forName()

    java.net.URLStreamHandlerFactory#createURLStreamHandler(String) (Returns different singleton objects, depending on a protocol)

    java.util.EnumSet#of()

    javax.xml.bind.JAXBContext#createMarshaller() and other similar methods.

  • 鉴定

    工厂方法可以通过创建方法来识别,这些方法从具体类创建对象,但将它们作为抽象类型或接口的对象返回。

参考

翻译整理自: https://refactoring.guru/design-patterns/factory-method