工厂方法
意图
工厂方法是一种创建性设计模式,它提供了在超类中创建对象的接口,但允许子类更改要创建的对象类型。

问题
假设您正在创建一个物流管理应用程序。你的应用程序的第一个版本只能处理卡车运输,所以你的大部分代码都在卡车
类。
一段时间后,你的应用变得非常受欢迎。每天你都会收到海运公司的数十个请求,要求将海运物流整合到应用程序中。

如果其余代码已经耦合到现有类,那么向程序中添加一个新类就不是那么简单了。
好消息,对吧?但是代码呢?目前,您的大部分代码都耦合到卡车
类。添加船只
进入应用程序需要对整个代码库进行更改。此外,如果稍后你决定向应用程序添加另一种类型的交通工具,你可能需要再次进行所有这些更改。
结果,你最终会得到非常糟糕的代码,充斥着根据传输对象的类别来切换应用程序行为的条件。
解决方案
工厂方法模式建议替换直接的对象构造调用(使用新
接线员)用特殊的电话工厂方法。方法创建的对象仍然是新
操作符,但它是从工厂方法内部被调用的。由工厂方法返回的对象通常被称为产品。

子类可以改变工厂方法返回的对象的类。
乍一看,这个更改可能毫无意义:我们只是将构造函数调用从程序的一部分移动到另一部分。但是,考虑一下:现在您可以在子类中重写factory方法,并更改由该方法创建的产品的类。
但是有一个小小的限制:只有当这些产品具有公共基类或接口时,子类才能返回不同类型的产品。此外,基类中的工厂方法应该将其返回类型声明为此接口。

所有产品必须遵循相同的界面。
例如,两者卡车
而且船
类应该实现运输
接口,该接口声明了调用的方法交付
.每个类别实现这种方法的方式不同:卡车通过陆路运输货物,轮船通过海上运输货物。中的工厂方法RoadLogistics
类中的factory方法返回卡车对象,而SeaLogistics
类返回舰船。

只要所有产品类都实现了一个公共接口,您就可以将它们的对象传递给客户端代码而不会破坏它。
使用工厂方法的代码(通常称为客户端代码)看不到各个子类返回的实际产品之间的差异。客户认为所有的产品都是抽象的运输
.客户机知道所有传输对象都应该具有交付
方法,但它是如何工作的对客户来说并不重要。
结构


的产品声明接口,该接口对所有可以由创建者及其子类生成的对象都是通用的。
具体的产品是产品接口的不同实现。
的创造者类声明返回新产品对象的工厂方法。此方法的返回类型必须与产品接口匹配,这一点很重要。
您可以将工厂方法声明为
摘要
强制所有子类实现它们自己的方法版本。作为一种替代方法,基本工厂方法可以返回一些默认的产品类型。注意,尽管它的名字,产品创造是不创造者的首要责任。通常,创建者类已经有一些与产品相关的核心业务逻辑。工厂方法有助于将此逻辑与具体产品类解耦。这里有一个类比:一个大型软件开发公司可以有一个程序员培训部门。然而,作为一个整体,公司的主要功能仍然是编写代码,而不是生产程序员。
具体的创造者重写基本工厂方法,使其返回不同类型的产品。
请注意,工厂方法不必这样做创建一直都有新实例。它还可以从缓存、对象池或其他源返回现有对象。
伪代码
这个例子说明了如何工厂方法可以用于创建跨平台UI元素,而无需将客户端代码耦合到具体的UI类。

跨平台对话框示例。
基地对话框
类使用不同的UI元素来呈现它的窗口。在不同的操作系统下,这些元素看起来可能略有不同,但它们的行为应该是一致的。Windows中的按钮在Linux中仍然是按钮。
类的逻辑开始起作用时,不需要重写对话框
为每个操作系统初始化。如果我们声明一个工厂方法,在基类中产生按钮对话框
类,我们可以稍后创建一个子类,从工厂方法返回windows样式的按钮。然后,子类从基类继承了大部分代码,但由于使用了factory方法,可以在屏幕上呈现类似windows的按钮。
要想让这个模式成立,基底对话框
类必须与抽象按钮一起工作:所有具体按钮遵循的基类或接口。这样代码就在里面了对话框
保持功能,无论它使用哪种类型的按钮。
当然,您也可以将这种方法应用于其他UI元素。方法中添加的每个新工厂方法对话框
,你更接近抽象工厂模式。不用担心,我们稍后会讨论这个模式。
//创建者类声明了工厂方法,必须返回product类的对象。创建者的子类//通常提供该方法的实现。创建者也可以提供一些工厂方法的默认实现。注意,尽管名字如此,但创建者的主要职责并不是创建产品。它通常包含一些核心业务逻辑,这些逻辑依赖于工厂方法返回的product //对象。子类可以通过覆盖// factory方法并从中返回不同类型的product //来间接地改变业务逻辑。method render() is //调用工厂方法创建一个产品对象。//现在使用产品。okButton.onClick(closeDialog) okButton.render() //具体创建者重写工厂方法来更改结果产品的类型。WebDialog extends Dialog is method createButton():Button is return new WindowsButton()类WebDialog extends Dialog is method createButton():Button is return new HTMLButton() //产品接口声明了所有//具体产品必须实现的操作。 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()
适用性
当您事先不知道您的代码应该使用的对象的确切类型和依赖关系时,请使用Factory方法。
工厂方法将产品构造代码与实际使用产品的代码分开。因此,更容易独立于其余代码扩展产品构造代码。
例如,要向应用程序添加一个新的产品类型,你只需要创建一个新的creator子类并覆盖其中的factory方法。
当您希望为库或框架的用户提供扩展其内部组件的方法时,请使用Factory方法。
继承可能是扩展库或框架默认行为的最简单方法。但是框架如何识别应该使用您的子类而不是标准组件呢?
解决方案是将跨框架构建组件的代码减少为单个工厂方法,并允许任何人除了扩展组件本身之外重写此方法。
让我们看看它是如何工作的。假设您使用开源UI框架编写一个应用程序。你的应用程序应该有圆形按钮,但框架只提供方形按钮。你扩展了标准按钮
班级带着辉煌RoundButton
子类。但现在你要告诉主要的UIFramework
类使用新的按钮子类而不是默认的按钮子类。
要实现这一点,需要创建一个子类UIWithRoundButtons
并重写其基框架类createButton
方法。而这个方法返回按钮
基类中的对象,则使子类返回RoundButton
对象。现在使用UIWithRoundButtons
类而不是UIFramework
.就是这样!
当您希望通过重用现有对象而不是每次都重新构建对象来节省系统资源时,请使用Factory方法。
在处理大型资源密集型对象(如数据库连接、文件系统和网络资源)时,您经常会遇到这种需求。
让我们考虑一下重用现有对象需要做些什么:
- 首先,您需要创建一些存储空间来跟踪所有创建的对象。
- 当有人请求一个对象时,程序应该在该池中寻找一个空闲对象。
- 然后返回给客户端代码。
- 如果没有空闲对象,程序应该创建一个新的对象(并将其添加到池中)。
这是一大堆代码!而且必须全部放在一个地方,这样就不会用重复的代码污染程序。
可能放置这些代码的最明显和最方便的地方是我们试图重用其对象的类的构造函数。但是,构造函数必须始终返回新对象通过定义。它不能返回现有实例。
因此,您需要有一个能够创建新对象以及重用现有对象的常规方法。这听起来很像工厂方法。
如何实施
使所有产品遵循相同的界面。这个接口应该声明在每个产品中都有意义的方法。
在创建者类中添加一个空的工厂方法。方法的返回类型应该匹配公共产品接口。
在创建者的代码中找到对产品构造函数的所有引用。将它们逐个替换为对工厂方法的调用,同时将产品创建代码提取到工厂方法中。
您可能需要向factory方法添加一个临时参数来控制返回产品的类型。
此时,工厂方法的代码可能看起来非常难看。它可能有一个大的
开关
语句,选择要实例化的产品类。不过别担心,我们很快就会修好的。现在,为工厂方法中列出的每种产品类型创建一组创建者子类。在子类中重写工厂方法,并从基方法中提取适当的构造代码。
如果产品类型太多,并且为所有产品类型创建子类没有意义,那么可以在子类中重用基类中的控制参数。
例如,假设您有以下类的层次结构:基类
邮件
类,其中包含两个子类:航空邮件
而且GroundMail
;的运输
类是飞机
,卡车
而且火车
.而航空邮件
类只使用飞机
对象,GroundMail
两者都可以使用卡车
而且火车
对象。您可以创建一个新的子类(例如TrainMail
)来处理这两种情况,但还有另一种选择。对象的工厂方法传递参数GroundMail
类来控制希望接收的产品。如果在所有提取之后,基本工厂方法变成空的,您可以使它抽象。如果还剩下一些内容,您可以将其作为方法的默认行为。
利与弊
- 您可以避免创建者和具体产品之间的紧密耦合。
- 单一责任原则.您可以将产品创建代码移动到程序中的某个位置,从而使代码更易于支持。
- 打开/关闭原则.您可以在不破坏现有客户端代码的情况下将新类型的产品引入程序。
- 代码可能会变得更加复杂,因为您需要引入许多新的子类来实现模式。最好的情况是将模式引入到现有的创建者类层次结构中。
与其他模式的关系
额外的内容
- 阅读我们的工厂比较如果您无法弄清楚各种工厂模式和概念之间的区别。