[设计模式] 工厂模式

初始化于: 2012-06-13

更新: 2013-09-22

参考书籍

《Head First 设计模式》4 工厂模式 —— 烘烤OO的精华[本文主要是基于这本的笔记]

《设计模式:可复用面向对象软件的基础》3.1 ABSTRACT FACTORY(抽象工厂) —— 对象创建型模式

《设计模式:可复用面向对象软件的基础》3.3 FACTORY METHOD(工厂方法) —— 对象创建型模式

相关博文

Programcreek: Java Design Pattern: Factory

Programcreek: Java Design Pattern: Abstract Factory


关键目的——"松耦合"

除了使用new操作符之外, 还有更多制造对象的方法.

实例化这个活动不应该总是公开地进行

初始化经常造成"耦合"问题

针对接口编程, 可以隔离掉以后系统可能发生的一大堆改变.

设计原则: 对扩展开放, 对修改关闭.

有一些要实例化的具体类, 究竟实例化哪个类, 要在运行时由一些条件来决定, 一有变化或扩展, 必须重新打开这段代码进行检查和修改. 这部分就是代码中变化的部分, 因此要考虑把它们从不变的部分分离出来.

比如生产比萨, 有许多种不同的比萨, 需要通过传入类型参数来判断到底要实例化哪种比萨, 但是比萨类型又增多了呢(总是会有新品种)? 此时你就必须修改创建对象的代码来支持更多类型的比萨(也有可能哪种比萨不生产了需要移除, 总之这部分是可变的).

怎么做? —— 找出会变化的方面, 把它们从不变的部分分离出来.

将创建对象的代码从普通方法中抽离, 放入另一个对象(工厂)中, 这个新对象只管如何创建具体对象.

工厂(factory)处理创建对象的细节.

所有的工厂都是用来封装对象的创建.

一开始的疑问:

单独出来有什么好处, 有变化时还是得修改工厂对象.

没考虑到的情况是: 可能还有其它地方会利用这个工厂来创建对象,,这个工厂可以有许多客户. 因此, 当以后实现改变时, 只需修改这个类, 而不会导致所有创建相关对象的地方都要修改.

将创建对象的代码集中在一个对象或方法中, 可以避免代码中的重复, 并且更方便以后的维护. 这也意味着客户在实例化对象时, 只会依赖于接口, 而不是具体类.

设计原则: 针对接口编程, 而不是针对实现编程.

我的理解: 在我们系统中的很多地方可能都需要用到相同的一段代码 —— 这段代码用来创建对象, 返回的对象的类型是一些具体类型的抽象类型, 创建的对象的具体类型在运行时才能确定, 这段代码应该略复杂, 可能会有许多if...else语句, 并且当系统需求变化, 比如加入新的具体类型时, 这段代码就需要修改 —— 这里应该很自然想到代码复用, 如果没有把这段代码提取出来, 当系统需求变化的时候, 许多地方都要同时修改, 这时候利用工厂就理所当然了. 另外一种理解就是, 既然有很多地方都要以相同的方式创建产品, 那把这些代码在一处管理总比到处都要管理来得强, 虽然你仍然需要实例化真正的对象(而不是抽象对象).

把工厂定义成静态方法(静态工厂)的缺点: 不能通过继承来改变创建方法的行为.

定义简单工厂

简单工厂其实不是一个设计模式, 反而比较像是一种编程习惯.

简单工厂由一个对象负责所有具体类的实例化(把全部的事情,在一个地方都处理完了), 任务太重.

在简单工厂中, 工厂是另一个由Creator创建者使用的对象.(组合)

简单工厂不具备工厂方法的弹性, 因为简单工厂不能变更正在创建的产品.

在设计模式中, 所谓的"实现一个接口"并"不一定"表示"写一个类, 并利用implement关键词来实现某个Java接口". "实现一个接口"泛指"实现某个超类型(可以是类或接口)的某个方法".

工厂方法模式

定义(意图): 工厂方法模式定义了一个创建对象的接口, 但由子类决定要实例化的类是哪一个. 工厂方法让类把实例化推迟到子类.

工厂方法用来处理对象的创建, 并将这样的行为封装在子类中. 这样, 客户程序中关于超类的代码就和子类对象创建代码解耦了.

工厂方法是抽象的, 依赖子类来处理对象的创建.

声明一个工厂方法

原本是由一个对象负责所有具体类的实例化, 现在通过对超类(抽象创建者类)做一些转变, 变成由一群子类来负责实例化. 实例化的责任被移到一个"方法"中, 此方法就如同是一个"工厂".

工厂方法用来处理对象的创建, 并将这样的行为封装在子类中.

abstract Product factoryMethod([String type]);
  • 工厂方法是抽象的, 所以依赖子类来处理对象的创建.(也可以不抽象, 提供默认行为)
  • 工厂方法必须返回一个产品. 超类中定义的方法, 通常使用到工厂方法的返回值.
  • 参数是可选的. 有参数时为"参数化工厂方法". 这里不一定要使用字符串, 可以创建代表参数类型的对象和使用静态常量或者Java 5支持的enum.

我的理解: 所有的产品都属于一个抽象产品类型A, 存在多个具体工厂的原因可能是可以对这些产品进行分类, 比如说有产品A1、A2、A3、A4、A5、A6都是A的子类, A1、A2、A3属于组1, A4、A5、A6属于组2, 具体工厂1生产组1, 具体工厂2生产组2, 工厂1只知道怎么生产组1的产品, 工厂2只知道怎么生产组2的产品, 这样就把产品的创建分配给多个子类了, 解决了简单工厂模式任务太重的缺点. 具体应用时要知道你要使用哪个具体工厂. (否则想象一下一堆的if...else...语句)

有一个"抽象的"(这里说抽象的原因应该是它可能或大多数情况下会被继承, 但并不意味着一定是个抽象类)创建者类, 包含抽象工厂方法, 它实现了所有操纵产品的方法, 但不实现工厂方法(也可以定义一个默认的工厂方法来产生某些具体的产品, 这么一来, 即使创建者没有任何子类, 依然可以创建产品).

相似的原则 —— 依赖倒置原则(Dependency Inversion Principle): 要依赖抽象, 不要依赖具体类.

不能让高层组件依赖低层组件, 而且, 不管高层或低层组件, "两者"都应该依赖于抽象.

我的理解: 上面的"抽象"工厂(高级组件), 不应该直接创建具体的产品(低层组件), 而应该只拥有抽象的产品(Product), 具体的产品由工厂方法创建, 高层组件并不关心具体生产出来的是什么产品, 因此它是依赖抽象的, 而低层组件只知道自己是抽象的产品的子类, 因此低层组件也只依赖于抽象.

倒置在哪?

本来高层组件依赖低层组件(许多箭头从上而下), 现在高层组件和低层组件都依赖同一个抽象, 这个抽象对于低层组件来说是高层的(箭头从下而上), 此时, 高层组件只有一条从上而下指向抽象的箭头.

倒置思考方式

一般: 先考虑高层, 然后考虑低层

倒置后: 先考虑有许多低层组件, 然后抽象出一个接口, 而高层组件只需依赖这个抽象, 而不管低层组件怎样.

指导方针(尽量, 而不是一定要遵循):

  • 变量不可以持有具体类的引用

    如果使用new, 就会持有具体类的引用. 可以改用工厂来避开这样的做法.(这里的一个原因是类一般都是可变的, 用工厂可封装改变, 特例: 字符串不可变)

  • 不要让类派生自具体类

    如果派生自具体类, 就会依赖具体类. 应该派生自一个抽象(接口或抽象类).

  • 不要覆盖基类中已实现的方法.

    如果覆盖基类已实现的方法, 那么你的基类就不是一个真正适合被继承的抽象. 基类中已实现的方法, 应该由所有的子类共享。

抽象工厂模式 Abstract Factory

对象创建型模式

创建产品家族

定义(意图): 抽象工厂模式提供一个接口, 用于创建(一系列)相关或相互依赖对象的家族, 而无需明确指定具体类.

别名: Kit

什么是产品家族?

例如制作比萨所需要的原料.

支持多种视感(look-and-feel)标准的用户界面工具包. 不同的视感风格为诸如滚动条、窗口和按钮等用户界面"窗口组件"定义不同的外观和行为. (每个软件使用的皮肤一类的)

每个具体工厂都能够生产一整组的产品.(把一群相关的产品集合起来)

适用性 —— 在以下情况可以使用Abstract Factory模式

  • 一个系统要独立于它的产品的创建、组合和表示时
  • 一个系统要由多个产品系列中的一个来配置时
  • 当你要强调一系列相关的产品对象的设计以便进行联合使用时
  • 当你提供一个产品类库, 而只想显示它们的接口而不是实现时

抽象工厂的方法经常以工厂方法的方式实现. 抽象工厂的任务是定义一个负责创建一组产品的接口. 这个接口内的每个方法都负责创建一个具体产品, 同时我们利用实现抽象工厂的子类来提供这些具体的做法. 所以, 在抽象工厂中利用工厂方法实现生产方法是相当自然的做法.

缺点: 如果加入新产品就必须改变接口, 所有的子类都要相应修改.

抽象工厂模式结构

Client仅使用由抽象工厂和抽象产品类声明的接口(解耦)

工厂方法和抽象工厂区别

工厂方法使用的是类, 而抽象工厂使用的是对象.

都负责创建对象, 但工厂方法用的是继承, 而抽象工厂通过对象的组合.

利用工厂方法创建对象, 需要扩展一个类(继承成子类), 并覆盖(或实现)它的工厂方法.

当需要创建产品家族和想让制造的相关产品集合起来时, 可以使用抽象工厂.

要点

  • 所有工厂都是用来封装对象的创建
  • 简单工厂, 虽然不是真正的设计模式, 但仍不失为一个简单的方法, 可以将客户程序从具体类解耦.
  • 工厂方法使用继承: 把对象的创建委托给子类, 子类实现工厂方法来创建对象.
  • 抽象工厂使用对象组合: 对象的创建被实现在工厂接口所暴露出来的方法中.
  • 所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合.
  • 工厂方法允许类将实例化延迟到子类进行.
  • 抽象工厂创建相关的对象家族, 而不需要依赖它们的具体类.
  • 依赖倒置原则, 指导我们避免依赖具体类型, 而要尽量依赖抽象.
  • 工厂是很有威力的技巧, 帮助我们针对抽象编程, 而不要针对具体类编程.