工厂方法模式(Factory Method Pattern): 简单工厂并不是一种设计模式

  1. 参考书籍: 《Design Patterns: Elements of Reusable Object-Oriented Software》
  2. Motivation for Simple Factory and Factory Method Pattern

工厂模式作为提及频率最为频繁的两个模式之一(另一个是单例模式), 却有着很多初学者都没有搞清楚的误区 。

工厂方法模式误区一: 简单工厂模式

首先, 23种设计模式中不存在简单工厂这一模式 , 只有 工厂方法抽象工厂 两种设计模式。 简单工厂只是一个工厂形式的方法或类,将创建对象的操作集中了起来,充其量只能算是一个套路,而不是一种模式

public Pizza createPizza(String type){
     if(type.equals("cheese")){
        return new CheesePizza();
     }else if (type.equals("pepperoni")){
        return new PepperoniPizza();
     }
     ...
}

工厂方法模式误区二: 抽象工厂模式与工厂方法模式的区别

很多中文的博文都没有阐述清楚抽象工厂模式与工厂方法模式的区别, 致使很多初学者对抽象工厂模式和工厂方法模式的应用场景差别理解得很模糊。

为了理解这两种模式的区别,首先需要分开介绍两种模式的适用场景

工厂方法模式(Factory Method)

  • 设计动机
    • 定义了一个父类(ClassA )之后,该父类( ClassA )需要实例化多个类(ClassE , ClassF, ClassG)中的其中一个 。 但是该父类希望将具体要实现E, F, G 中的哪一个类留给其子类(SubClassA)来决定。
    • 举例: 假设现在要编写一个支持屠宰家畜的框架, 家畜的类型包括牛羊等, 于是希望先定义一个屠宰场类 KillHouse, 该屠宰场会在调用KillAnimal(Animal animal) 方法时会接受运行时传入的参数(Sheep、Chicken ) 。 现在假设, 宰杀家畜这个行为需要一把刀(Knife), 但是宰杀不同类型的牲畜的刀不一样,毕竟杀鸡不能用牛刀嘛, 但是这些工具都提供了 chop() 的方法, 对于屠宰厂来说, 只需要调用chop() 方法即可, 但是屠宰场是没办法提前预知要实例化哪一种刀的, 这个决定必须留给其子类杀牛场和杀鸡场来决定,这个时候就是应用工厂方法模式的时机
abstract public class KillHouse {

    protected abstract Knife createKnife();

    public void killAnimal(Animal animal)
    {
        // 有一些通用的准备步骤
        commonPrepareWork();
        // 有一些因动物类别而不同的准备步骤需要
        customPrepareWork();

        Knife knife = createKnife();
        knife.chop(animal); // 切割动物的操作
        // .... 有一些清理步骤
    }

    protected abstract void customPrepareWork();

    private void commonPrepareWork() {
        // 一些操作
    }

}
public class KillCowHouse extends KillHouse {
    @Override
    protected Knife createKnife() {
        return new CowKnife();
    }

    @Override
    protected void customPrepareWork() {
        //一些操作
    }

}
public class Knife {

    public void chop(Animal animal) {
    }
}
public class CowKnife extends Knife {
}

通过以上的说明以及例子,其实可以总结出工厂方法模式的几个要素:

  • 工厂模式的核心是把一个实例化操作延迟到了子类。
    • 注意点一: 这里并不是说简单的把实例化操作集中到一个Factory 类就是工厂模式, 其中核心的参与者至少有四个:
      • 创建者的父类
      • 创建者的子类
      • 被创建者的父类
      • 被创建者的子类
    • 注意点二: 之所以要用工厂方法模式来把实例化操作留给子类完成的原因是父类没有办法预知应该实例化哪一种对象
    • 注意点三: 之所以存在创建者的父类和子类, 而不是简简单单只有一个创建者累的原因是, 父类中的有一些操作, 希望被多个子类复用。
    • 注意点四: 在文章中举的例子场景中, 实际上做了两个假设:“杀鸡不能用牛刀”和 “并没有一把万用刀”, 所以使用了创建对象的方法createKnife 被定义成了抽象的, 这样强制了子类去实现该方法。 相反,此处如果不使用工厂模式, 采用如下写法。
public class KillHouse {

    protected Knife = null;

    public void killAnimal(Animal animal)
    {
        // 有一些通用的准备步骤
        commonPrepareWork();
        // 有一些因动物类别而不同的准备步骤需要
        customPrepareWork();

        knife.chop(animal); // 切割动物的操作
        // .... 有一些清理步骤
    }

    protected abstract void customPrepareWork();

    private void commonPrepareWork() {
        // 一些操作
    }

}

此时 , KillHouse的子类在继承时, 就必须要自己记得去对Knife 成员变量进行赋值, 如果忘记赋值, 就会导致空引用错误。 当然, 我们也可以认为牛刀可以宰杀种家禽,将Knife 默认初始化为 CowKnife 的实例, 但是这样就会导致 “杀鸡用了牛刀”等我们不一定希望看到的情形。

抽象工厂模式(Factory Method)

  • 设计动机
    • 提供了一个接口用于创建一个相互依赖的产品系列
    • 举例: 假设有一个用于编写用户界面的 工具包/ 类库, 该类库支持编写不同风格的操作界面(Windows 风格, Mac 风格, Metal 风格) 。不同风格的界面组件(按钮, 滚动条, 菜单)的外观和交互方式都会有一些或大或小的差别。
    • 为了支持不同风格操作界面的切换, 应用程序在编写界面时, 显然不能把特定类型的风格组件硬编码到应用层级的代码中。
      • 此时并不能利用简单工厂套路为每个组件定义一个Facotry( ButtonFactory, ScrollBarFactory , MenuFactory ), 通过向getComponent(String style)传入的参数来决定获取哪一个类型的组件。 因为如果这样做, 应用程序在通过不同的Factory 获取组件的时候, 都必须传入类型参数, 因为不同风格的组件肯定是不能被混用的,一旦在编写程序的过程中, 同一个应用中,在不同的地方传入了不同的风格参数, 除了会导致外观的不一致以外, 由于同一风格的组件之间相互还会存在一些依赖,交互上也可能发生错误 。
    • 此时希望达到的效果是,我们只需要向一个面向Factory 编程, 可以从这个Factory 中获取到不同类型的产品, getButton(), getScrollBar(), getMenu(), 而我们只需要在程序运行时, 在唯一的一处设置风格变量, 工具类会根据该变量, 在不同位置调用了获取不同组件的方法, 返回风格一致的组件 。
public interface WidgetFactory {
    Button createButton();
    ScrollBar createScrollBar();
    Menu createMenu();
}
public class MacStyleFactory implements WidgetFactory{
    @Override
    public Button createButton() {
        return MacButton
    }

    @Override
    public ScrollBar createScrollBar() {
        return MacScrollBar;
    }

    @Override
    public Menu createMenu() {
        return MacMenu;
    }
}
  • 注意点一: 在以上的例子中, 抽象工厂模式的最大好处是, 通过一个接口, 和不同风格的工厂实现类, 保证了用户在使用工具包编程时与其具体的组件风格完全解耦,获取组件时, 只需要跟一个Factory交互,也只能够跟一个风格的Factory交互(可以通过单例模式保证), 强制保证了一个应用中不会产生不一致风格的组件,致使发生显示错误或交互错误。
  • 注意点二: 由于抽象工厂提供了的接口固定了产生的产品系列, 这相当于假设了不同风格的产品类型都是一致的, 假如说Windows 风格增加了一个在Mac风格中不存在的组件(Door) , 抽象工厂模式就失效了,因为要继承抽象工厂接口, 得实现其中的全部方法, 而MacFactory 肯定是没有办法实现createDoor 这个方法的。

工厂方法模式与抽象工厂模式区别总结

通过之前长篇幅的描述, 就可以发现:

  • 抽象工厂模式和工厂方法模式要解决的问题出发点完全不同。

    • 抽象工厂方法模式用于创造一系列相互依赖的产品, 且强制了这些产品只能被同时使用。
    • 工厂方法模式用于将实例化的操作延迟给子类完成。 所以使用了工厂方法的标志是继承关系, 是父类留了一个实例化对象的方法(可以是抽象方法, 也可以是已经实现的方法), 可供子类去实现或重写, 而不是有一个工厂类,集中了创建对象的操作
    • 抽象工厂模式和工厂方法模式的联系在于, 抽象工厂方法模式通常是利用工厂方法模式实现的。 例如: WidgetFactory 中的createButton, createScrollBar等方法都留给了子类实现。
  • 如果要给抽象工厂工厂方法模式各给一个简短易记的特征,那应该是:

  • 抽象工厂–》 固定的产品系列
  • 工厂方法–》 创建操作的延迟
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值