设计模式【创建型模式】【工厂方法】

意图

工厂方法模式 是一种创建型设计模式,其在父类种提供一个创建对象的方法,允许子类决定是实例化对象的类型。

问题

假设有一款物流管理应用,最初版本只能处理卡车运输,因此绝大部分代码都在位于名为 「卡车」的类种。

一段时间之后,应用极受欢迎,每天都可以收到几十次海运公司的请求,希望可以应用支持海上物流功能。

如果代码其余部分与现有类已存在耦合关系,那么向程序中添加新类其实并没有那么容易了。

目前绝大部分代码都与「卡车」相关,在程序中添加「轮船」类需要修改全部代码,如果以后需要支持另外一种运输方式,很可能需要再次对这些代码进行大幅度的修改,导致最后不得不编写繁复的代码,根据不同的运输对象类,在应用中进行不同的处理。

解决方案

工厂方法模式建议使用特殊的工厂方法代替对于对象构造函数的直接调用(即使用new 运算符),对象仍会通过new运算符创建,只是该运算符改在工厂方法中调用,工厂方法返回的对象将会被称为「产品」。

可能乍看之下这种改动没有很大的意义,我们只是改变了程序中调用构造函数的位置而已,但是思考之下会发现,现在可以在子类中重写工厂方法,从而改变创建产品的类型。

有一点需要注意:当这些产品具有共同的基类或者接口的时候,子类才能返回不同类型的产品,同时基类中的工厂方法应将其返回类型声明为共有接口。

「卡车」和「轮船」类都必须实现「运输」接口,该接口声明了一个名为「deliver 交付」的方法,每个类都将以不同的方法实现该方法

「卡车」走陆路交付货物,「轮船」走海路交付货物,录入运输类中的工厂方法返回卡车对象,而海路则返回轮船对象。

调用工厂方法的代码(通常被称为客户端代码)无需了解不同子类返回实际对象之间的差别。客户端将所有产品视为抽象的「运输」

客户端知道素有的运输对象都提供「交付」方法,但是不关心具体实现方式。

工厂方法模式结构

产品(Product)

将会对接口进行声明,对于所有由创建者及子类构建的对象,这些接口都是通用的。

具体产品(Concrete Products)

是产品接口的不同实现

创建者(Creator)

类声明返回产品对象的工厂方法,该方法的返回对象类型必须与产品接口相匹配。也可以将工厂方法声明为抽象方法,强制要求每个子类以不同方式实现该方法,或者也可以在基础工厂方法中返回默认类型。

注意:尽管它的名字是创建者,但他的主要职责并不是创建产品,一般来说,创建者类包含一些与产品相关的核心业务逻辑。工厂方法将这些逻辑处理从具体产品中分离出来。

具体创建者(Concrete Creators)

将会重写基础工厂方法,使其返回不同类型的产品。

注意:并不一定每次调用工厂方法都会创建新的实例,工厂方法也可以返回缓存,对象池或其他来源的已有对象。

伪代码

以下示例掩饰了如何使用工厂方法开发跨平台UI 组件,同时避免客户代码与具体UI类之间的耦合。

基础对话框类使用不同的UI组件渲染窗口,在不同的操作系统下,这些组件外观或许有不同,但是功能保持一致,windows系统的按钮在linux系统中仍是按钮。

如果使用工厂方法,就不需要为每种操作系统重写对话框逻辑。如果我们声明了一个在基本对话框类中生成按钮的工厂方法。

那么我们可以创建一个对话框子类,并使其通过工厂方法返回windows样式。子类将继承对话框基础的大部分代码同时在屏幕上根据 Windows 样式渲染按钮。

// 创建者类声明的工厂方法必须返回一个产品类的对象。创建者的子类通常会提供
// 该方法的实现。
class Dialog is
    // 创建者还可提供一些工厂方法的默认实现。
    abstract method createButton():Button
    // 请注意,创建者的主要职责并非是创建产品。其中通常会包含一些核心业务
    // 逻辑,这些逻辑依赖于由工厂方法返回的产品对象。子类可通过重写工厂方
    // 法并使其返回不同类型的产品来间接修改业务逻辑。
    method render() is
        // 调用工厂方法创建一个产品对象。
        Button okButton = createButton()
        // 现在使用产品。
        okButton.onClick(closeDialog)
        okButton.render()
// 具体创建者将重写工厂方法以改变其所返回的产品类型。
class WindowsDialog extends Dialog is
    method createButton():Button is
        return new WindowsButton()
class WebDialog extends Dialog is
    method createButton():Button 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() 

工厂方法模式适合应用场景 

  • 当你在编写代码的过程中,无法预知对象确切类别以及依赖的时候可以使用工厂方法。

  • 如果你希望用户能扩展你的软件库或者框架内部组件的时候可以使用工厂方法。

  • 如果你希望服用现有对象来节省系统资源,而不是每次都重新创建对象,可以使用工厂方法。

实现方式

  • 让所有产品都遵循同一个接口,该接口必须声明对所有产品都有意义的方法。

  • 在创建类中添加一个空的工厂方法,该方法的返回类型必须遵循通用的产品接口。

  • 创建者代码中找到对于产品构造函数的所有引用,将它们依次替换为对工厂方法的调用,同时将创建产品的代码移入工厂方法。

  • 工厂方法中每种产品编写一个创建者子类,然后在子类中重写工厂方法,并将基本方法中的相关创建代码移动到工厂方法中。

工厂方法模式优缺点

优点:

  • 你可以避免创建者和具体产品之间的紧密耦合

  • 单一职责原则,你可以将产品创建代码放在程序的单一位置,从而使得代码更容易维护。

  • 开闭原则,无需更改现有客户端代码,你就可以在程序中引入新的产品类型。

缺点:

  • 应用工厂方法模式需要引入许多新的子类,代码会变得更复杂,最好的情况是将该模式引入创建者累的现有层次结构中。

与其他模式的关系

  • 在许多设计工作的初期都会使用工厂方法模式(较为简单,而且可以方便的通过子类进行定制)随后演化为使用抽象工厂模式,原型模式或者生成器模式

  • 抽象工厂模式通常基于一组工厂方法,但你也可以使用原型模式来生成这些类的方法。

  • 可以同时使用工厂方法和迭代器模式来让子类集合返回不同类型的迭代器,并使得迭代器与集合匹配。

  • 原型不基于继承,因此没有继承的缺点,另一方面原型需要被复制对象进行复杂的初始化,工厂方法基于继承,但是它不需要初始化步骤。

  • 工厂方法是模版方法模式的一种特殊形式,同时工厂方法可以作为一个大型模版方法中的一个步骤

猜你喜欢

转载自blog.csdn.net/u013946285/article/details/112857678