工厂方法
Factory Method | 工厂方法
工厂方法是一种创建型设计模式,其在父类中提供一个创建对象的接口,允许子类决定实例化对象的类型,即子类可以修改工厂方法返回的对象类型。工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用(即使用 new 运算符)。对象仍将通过 new 运算符创建,只是该运算符改在工厂方法中调用罢了;工厂方法返回的对象通常被称作“产品”。
但有一点需要注意:仅当这些产品具有共同的基类或者接口时,子类才能返回不同类型的产品,同时基类中的工厂方法还应将其返回类型声明为这一共有接口。
譬如上例中,卡车(Truck)和轮船(Ship)类都必须实现运输(Transport)接口,该接口声明了一个名为交付(deliver)的方法。每个类都将以不同的方式实现该方法:卡车走陆路交付货物,轮船走海路交付货物。陆路运输(RoadLogistics)类中的工厂方法返回卡车对象,而海路运输(SeaLogistics)类则返回轮船对象。
优劣对比
工厂方法可以避免创建者和具体产品之间的紧密耦合,将产品创建代码放在程序的单一位置,从而使得代码更容易维护;并且无需更改现有客户端代码,你就可以在程序中引入新的产品类型。不过应用该模式需要引入许多新的子类,代码可能会因此变得更复杂。最好的情况是将该模式引入创建者类的现有层次结构中。
总结而言,工厂方法适用于以下场景:
-
在编写代码的过程中,如果无法预知对象确切类别及其依赖关系时,可使用工厂方法。工厂方法将创建产品的代码与实际使用产品的代码分离,从而能在不影响其他代码的情况下扩展产品创建部分代码。例如,如果需要向应用中添加一种新产品,你只需要开发新的创建者子类,然后重写其工厂方法即可。
-
希望用户能扩展你软件库或框架的内部组件,可使用工厂方法。将各框架中构造组件的代码集中到单个工厂方法中,并在继承该组件之外允许任何人对该方法进行重写。
-
复用现有对象来节省系统资源,而不是每次都重新创建对象,可使用工厂方法。在处理大型资源密集型对象(比如数据库连接、文件系统和网络资源)时,需要有一个既能够创建新对象,又可以重用现有对象的普通方法。
实现方式
产品(Product)将会对接口进行声明。对于所有由创建者及其子类构建的对象,这些接口都是通用的。具体产品(Concrete Products)是产品接口的不同实现。创建者(Creator)类声明返回产品对象的工厂方法。该方法的返回对象类型必须与产品接口相匹配。
-
首先让所有产品都遵循同一接口,该接口必须声明对所有产品都有意义的方法。
-
然后在创建类中添加一个空的工厂方法。该方法的返回类型必须遵循通用的产品接口。
-
在创建者代码中找到对于产品构造函数的所有引用。将它们依次替换为对于工厂方法的调用,同时将创建产品的代码移入工厂方法。
-
最后,为工厂方法中的每种产品编写一个创建者子类,然后在子类中重写工厂方法,并将基本方法中的相关创建代码移动到工厂方法中。如果应用中的产品类型太多,那么为每个产品创建子类并无太大必要,这时你也可以在子类中复用基类中的控制参数。
你可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法。或者,你也可以在基础工厂方法中返回默认产品类型。注意,尽管它的名字是创建者,但他最主要的职责并不是创建产品。一般来说,创建者类包含一些与产品相关的核心业务逻辑。工厂方法将这些逻辑处理从具体产品类中分离出来。打个比方,大型软件开发公司拥有程序员培训部门。但是,这些公司的主要工作还是编写代码,而非生产程序员。
具体创建者(Concrete Creators)将会重写基础工厂方法,使其返回不同类型的产品。注意,并不一定每次调用工厂方法都会创建新的实例。工厂方法也可以返回缓存、对象池或其他来源的已有对象。
案例:跨平台组件
以下示例演示了如何使用工厂方法开发跨平台 UI(用户界面)组件,并同时避免客户代码与具体 UI 类之间的耦合。
基础对话框类使用不同的 UI 组件渲染窗口。在不操作系统下,这些组件外观或许略有不同,但其功能保持一致。Windows 系统中的按钮在 Linux 系统中仍然是按钮。
如果使用工厂方法,就不需要为每种操作系统重写对话框逻辑。如果我们声明了一个在基本对话框类中生成按钮的工厂方法,那么我们就可以创建一个对话框子类,并使其通过工厂方法返回 Windows 样式按钮。子类将继承对话框基础类的大部分代码,同时在屏幕上根据 Windows 样式渲染按钮。
如需该模式正常工作,基础对话框类必须使用抽象按钮(例如基类或接口),以便将其扩展为具体按钮。这样一来,无论对话框中使用何种类型的按钮,其代码都可以正常工作。
// 创建者类声明的工厂方法必须返回一个产品类的对象。创建者的子类通常会提供
// 该方法的实现。
class Dialog is
// 创建者还可提供一些工厂方法的默认实现。
abstract method createButton()
// 请注意,创建者的主要职责并非是创建产品。其中通常会包含一些核心业务
// 逻辑,这些逻辑依赖于由工厂方法返回的产品对象。子类可通过重写工厂方
// 法并使其返回不同类型的产品来间接修改业务逻辑。
method render() is
// 调用工厂方法创建一个产品对象。
Button okButton = createButton()
// 现在使用产品。
okButton.onClick(closeDialog)
okButton.render()
// 具体创建者将重写工厂方法以改变其所返回的产品类型。
class WindowsDialog extends Dialog is
method createButton() is
return new WindowsButton()
class WebDialog extends Dialog is
method createButton() is
return new HTMLButton()
// 产品接口中将声明所有具体产品都必须实现的操作。
interface Button is
method render()
method onClick(f)
// 具体产品需提供产品接口的各种实现。
class WindowsButton implements Button is
method render(a, b) is
// 根据 Windows 样式渲染按钮。
method onClick(f) is
// 绑定本地操作系统点击事件。
class HTMLButton implements Button is
method render(a, b) is
// 返回一个按钮的 HTML 表述。
method onClick(f) is
// 绑定网络浏览器的点击事件。
class Application is
field dialog: Dialog
// 程序根据当前配置或环境设定选择创建者的类型。
method initialize() is
config = readApplicationConfigFile()
if (config.OS == "Windows") then
dialog = new WindowsDialog()
else if (config.OS == "Web") then
dialog = new WebDialog()
else
throw new Exception("错误!未知的操作系统。")
// 当前客户端代码会与具体创建者的实例进行交互,但是必须通过其基本接口
// 进行。只要客户端通过基本接口与创建者进行交互,你就可将任何创建者子
// 类传递给客户端。
method main() is
this.initialize()
dialog.render()
工厂模式比较
简单工厂/静态工厂,工厂方法,抽象工厂都属于设计模式中的创建型模式。其主要功能都是帮助我们把对象的实例化部分抽取了出来,优化了系统的架构,并且增强了系统的扩展性。
-
简单工厂模式对具体产品的创建类别和创建时机的判断是混和在一起的,不能形成简单工厂模式的继承结构,不符合开放封闭原则。
-
在工厂方法模式中,对于存在继承等级结构的产品树,产品的创建是通过相应等级结构的工厂创建的。