工厂方法
也被称作:抽象构造
目的
工厂方法是一种创建设计模式,它在父类中提供了创建对象的接口但允许子类更改将要创建的对象的类型。
问题
假设您正在创建一个物流管理应用程序。你的应用的第一个版本只能处理卡车运输,所以你的大部分代码都存在于卡车类中。
不久之后,你的应用就变得非常受欢迎。每天你都会收到几十个海运公司的请求,要求将海运物流整合到应用程序中。
但是目前大多数代码都与卡车类耦合在一起。将船类添加到应用中需要对整个代码库进行修改。此外如果之后你决定在应用程序中添加另一种类型的交通工具,你可能需要再次进行所有这些更改。
结果您将得到非常糟糕的代码,其中充满了根据传输对象的类来切换应用程序行为的条件。
解决方案
工厂方法模式建议将直接对象构造调用(使用new操作符)替换为对特殊工厂方法的调用。对象仍然是通过new操作符创建的,但它是从工厂方法内部调用的。工厂方法返回的对象通常称为产品。
乍一看,这种更改可能毫无意义:我们只是将构造函数调用从程序的一个部分移到另一个部分。但是请考虑这一点:现在您可以在子类中重写工厂方法并更改由该方法创建的产品的类。
但是有一个轻微的限制:只有当这些产品具有公共的基类或接口时,子类才能返回不同类型的产品。此外,基类中的工厂方法应该将其返回类型声明为此接口。
例如,Truck和Ship类都应该实现Transport接口,该接口声明了一个名为deliver的方法。每个类都以不同的方式实现这个方法:卡车通过陆地运送货物,轮船通过海上运送货物。RoadLogistics类中的工厂方法返回卡车对象,而SeaLogistics类中的工厂方法返回船舶。
使用工厂方法的代码(通常称为客户端代码)看不到各种子类返回的实际产品之间的差异。客户将所有产品视为抽象运输。客户端知道所有传输对象都应该具有deliver方法,但它的具体工作方式对客户端并不重要。
结构
- 声明了
Product
这个接口,它对所有可以由创建者及其子类产生的对象都是通用的。 Concrete Product
是Product接口的不同实现。Creator
类声明了返回新产品对象的工厂方法。这个方法的返回类型必须与产品接口匹配,这一点很重要。您可以将工厂方法声明为抽象的,以强制所有子类实现它们自己的方法版本。作为一种替代方法,基本工厂方法可以返回某种默认产品类型。请注意尽管名称如此,产品创建并不是创建者的主要职责。通常,创建者类已经拥有一些与产品相关的核心业务逻辑。工厂方法有助于将这种逻辑从具体的产品类中分离出来。这里有一个类比:一个大型软件开发公司可以有一个程序员培训部门。然而整个公司的主要职能仍然是编写代码而不是培养程序员。Concrete Creator
重写基本工厂方法,因此它返回不同类型的产品。
注意工厂方法不必一直创建新实例。它还可以从缓存、对象池或其他源返回现有对象。
伪码
这个例子演示了如何使用Factory方法来创建跨平台的UI元素而不用将客户端代码耦合到具体的UI类。
基类Dialog
使用不同的UI元素来呈现它的窗口。在不同的操作系统下,这些元素可能看起来有些不同但它们仍然应该保持一致的行为。Windows中的按钮在Linux中仍然是按钮。
当工厂方法开始发挥作用时,您不需要为每个操作系统重写Dialog
类的逻辑。如果我们在基本的Dialog
类中声明一个工厂方法来产生按钮,我们以后可以创建一个子类,从工厂方法返回windows样式的按钮。然后子类继承基类的大部分代码但是由于工厂方法,可以在屏幕上呈现windows外观的按钮。
要使此模式工作,基本Dialog类必须与抽象按钮一起工作:所有具体按钮都遵循的基类或接口。通过这种方式Dialog
中的代码保持功能性,无论它处理的是哪种类型的按钮。
当然您也可以将此方法应用于其他UI元素。但是随着您向Dialog
中添加的每一个新的工厂方法,您将更接近抽象工厂模式。不要害怕我们稍后会讨论这个模式。
// The creator class declares the factory method that must
// return an object of a product class. The creator's subclasses
// usually provide the implementation of this method.
class Dialog is
// The creator may also provide some default implementation
// of the factory method.
abstract method createButton():Button
// Note that, despite its name, the creator's primary
// responsibility isn't creating products. It usually
// contains some core business logic that relies on product
// objects returned by the factory method. Subclasses can
// indirectly change that business logic by overriding the
// factory method and returning a different type of product
// from it.
method render() is
// Call the factory method to create a product object.
Button okButton = createButton()
// Now use the product.
okButton.onClick(closeDialog)
okButton.render()
// Concrete creators override the factory method to change the
// resulting product's type.
class WindowsDialog extends Dialog is
method createButton():Button is
return new WindowsButton()
class WebDialog extends Dialog is
method createButton():Button is
return new HTMLButton()
// The product interface declares the operations that all
// concrete products must implement.
interface Button is
method render()
method onClick(f)
// Concrete products provide various implementations of the
// product interface.
class WindowsButton implements Button is
method render(a, b) is
// Render a button in Windows style.
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.
class Application is
field dialog: Dialog
// The application picks a creator's type depending on the
// current configuration or environment settings.
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("Error! Unknown operating system.")
// The client code works with an instance of a concrete
// creator, albeit through its base interface. As long as
// the client keeps working with the creator via the base
// interface, you can pass it any creator's subclass.
method main() is
this.initialize()
dialog.render()
应用
当你事先不知道你的代码应该处理的对象的确切类型和依赖关系时使用工厂方法。
工厂方法将产品构造代码从实际使用产品的代码中分离出来。因此更容易独立于其他代码扩展产品构造代码。例如要向应用程序添加一个新的产品类型,您只需要创建一个新的creator子类,并覆盖其中的工厂方法。
当你想为你的库或框架的用户提供一种扩展其内部组件的方法时使用工厂方法。
继承可能是扩展库或框架默认行为的最简单方法。但是框架如何识别应该使用你的子类而不是标准组件呢?解决方案是将跨框架构建组件的代码减少到单个工厂方法中并且除了扩展组件本身之外,允许任何人重写该方法。
让我们看看它是如何工作的。假设您使用开源UI框架编写一个应用程序。你的应用程序应该有圆形的按钮,但框架只提供方形的按钮。您可以用一个漂亮的RoundButton
子类来扩展标准的Button
类。但是现在您需要告诉主UIFramework
类使用新的按钮子类而不是默认的。
为了实现这一点你从一个基本框架类中创建一个子类UIWithRoundButtons
并覆盖它的createButton方法。当这个方法返回基类中的Button
对象时,你让你的子类返回RoundButton
对象。现在使用UIWithRoundButtons
类来代替UIFramework
。就是这样!
当您希望通过重用现有对象而不是每次重新构建它们来节省系统资源时,请使用工厂方法。
在处理大型、资源密集型对象(如数据库连接、文件系统和网络资源)时,您经常会遇到这种需求。
让我们想想要重用一个现有的对象需要做些什么:
- 首先,您需要创建一些存储来跟踪所有创建的对象。
- 当有人请求一个对象时,程序应该在池中查找空闲对象。
- 然后将其返回给客户机代码。
- 如果没有空闲对象,程序应该创建一个新对象(并将其添加到池中)。
这是很多代码!它必须放在一个单独的地方,这样你就不会用重复的代码污染程序。可能放置这些代码的最明显和最方便的地方是我们试图重用其对象的类的构造函数。但是根据定义构造函数必须始终返回新对象。它不能返回现有的实例。
因此您需要有一个能够创建新对象和重用现有对象的常规方法。这听起来很像工厂方法。
如何实现
-
使所有产品遵循相同的界面。这个接口应该声明在每个产品中都有意义的方法。
-
在创建者类中添加一个空工厂方法。方法的返回类型应该与通用产品接口匹配。
-
在创建者的代码中找到对产品构造函数的所有引用。将它们逐个替换为对工厂方法的调用,同时将产品创建代码提取到工厂方法中。您可能需要向工厂方法添加一个临时参数来控制返回产品的类型。此时工厂方法的代码可能看起来非常难看。它可能有一个较大的switch语句,用于选择实例化哪个产品类。不过别担心,我们很快就会修好的。
-
现在为工厂方法中列出的每种产品类型创建一组creator子类。在子类中重写工厂方法并从基方法中提取适当的构造代码位。如果有太多的产品类型并且为所有产品类型创建子类没有意义,您可以在子类中重用基类中的控制参数。
-
例如假设你有以下类的层次结构:基本的Mail类有两个子类:AirMail和GroundMail;Transport类有Plane,Truck和Train。当AirMail类只使用Plane对象时,GroundMail可以同时使用Truck和Train对象。你可以创建一个新的子类(比如TrainMail)来处理这两种情况。但还有另一种选择,客户端代码可以向GroundMail类的工厂方法传递一个参数以控制它想要接收的产品。
-
如果在所有抽象之后,基本工厂方法变成空的,你可以将其转为抽象类。如果还有一些东西没有处理您可以将其作为该方法的默认行为。
正反面
正面因素
- 你避免了创造者和具体产品之间的紧密耦合。
- 单一责任原则。您可以将产品创建代码移动到程序中的一个位置,使代码更易于支持。
- 打开/关闭原则。您可以在不破坏现有客户端代码的情况下将新类型的产品引入程序。
反面因素
- 代码可能会变得更加复杂,因为您需要引入许多新的子类来实现模式。最好的情况是将模式引入到现有的创建者类层次结构中。
同其他模式的关系
-
许多设计开始使用工厂方法(更简单,更可通过子类定制),然后发展到抽象工厂、原型或构建器(更灵活,但更复杂)。
-
抽象工厂类通常基于一组工厂方法,但您也可以使用原型来组合这些类上的方法。
-
您可以使用工厂方法和迭代器来让集合的子类返回与集合兼容的不同类型的迭代器。
-
原型不是基于继承的,所以它没有缺点。另一方面,原型需要对克隆对象进行复杂的初始化。工厂方法基于继承但不需要初始化步骤。
-
工厂方法是模板方法的特例化。同时工厂方法可以作为大型模板方法中的一个步骤。
发表评论