简单工厂模式

317 阅读5分钟

简单工厂模式

Reference

[1] bugstack.cn/md/develop/…

[2] c.biancheng.net/view/1397.h…

[3] refactoringguru.cn/design-patt…

[4] cmsblogs.com/article/140…

[5] blog.csdn.net/lovelion

场景

有一个披萨的项目,要便于披萨种类的拓展,便于维护

  1. 披萨种类很多,ChessPizza、GreekPizza ...
  2. 披萨制作过程有 prepare、bake、cut、box
  3. 披萨店有订购披萨的功能

一般思路

构建一个父类 Pizza ,里面有所有的制作方法,让不同种类的披萨去继承改类

image-20220109180646147
public class OrderPizza {

    private String orderType;

    private Pizza pizza;

    private static final String cheeseType = "cheese";

    private static final String greekType = "greek";

    private static final String pepperType = "pepper";

    public OrderPizza(String orderType) {

        this.orderType = orderType;

        if (cheeseType.equals(orderType)) {
            pizza = new CheesePizza();
        } else if (greekType.equals(orderType)) {
            pizza = new GreekPizza();
        } else if (pepperType.equals(orderType)) {
            pizza = new PepperPizza();
        } else {
            // 默认处理
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
    }

}

优缺点

  • 优点就是比较好理解,可以简易地进行修改操作
  • 缺点,首先违反了 ocp 原则,如果要给类新增功能时,会修改到 orderPizza 的代码

改进思路

  • 修改代码是可以接受的,但是如果我们在其他地方也有创建 Pizza 的代码,也就意味着这次修改的风险很大,有很多处
  • 可以把创建 Pizza 对象封装到一个类中,这样我们有新的 Pizza 种类时,只需要修改类即可,其他有创建到 Pizza 对象的代码就不需要修改了,这就是简单工厂模式

简单工厂介绍

简单工厂模式并不属于GoF23个经典设计模式,但通常将它作为学习其他工厂模式的基础,它的设计思想很简单,其基本流程如下:

  1. 首先将需要创建的各种不同对象(例如各种不同的 Pizza 对象)的相关代码封装到不同的类中,这些类称为具体产品类
  2. 将它们公共的代码进行抽象和提取后封装在一个抽象产品类中,每一个具体产品类都是抽象产品类的子类;
  3. 然后提供一个工厂类用于创建各种产品,在工厂类中提供一个创建产品的工厂方法,该方法可以根据所传入的参数不同创建不同的具体产品对象;
  4. 客户端只需调用工厂类的工厂方法并传入相应的参数即可得到一个产品对象。

简单工厂模式是由一个工厂对象决定创建出哪一 种产品类的实例,它定义了一个创建对象的类,让这个类来封装实例化对象的行为

image-20220109185526928

使用简单工厂改进

按照上面的分析,我们可以把创建Pizza对象的任务交给一个类来实现,这个类就是工厂类 SimpleFactory,这里解耦了 OrderPizza 等类的与 Pizza 等对象,而是让 SimpleFactory 去依赖具体的Pizza类,而订单类就只需要去依赖工厂类即可(聚合或者组合都可以,自行选择)

image-20220109182814068

public class SimpleFactory {

    public Pizza createPizza(Enum pizzaType) {
        Pizza pizza = null;

        if (PizzaType.cheeseType.equals(pizzaType)) {
            pizza = new CheesePizza();
        } else if (PizzaType.greekType.equals(pizzaType)) {
            pizza = new GreekPizza();
        } else if (PizzaType.pepperType.equals(pizzaType)) {
            pizza = new PepperPizza();
        } else {
            // 默认处理
        }

        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();

        return pizza;
    }

    // 也可以写成静态方法,通过类引用去调用
    public static  Pizza createPizza(Enum orderType) {

    }

}

public class OrderPizza{
    public OrderPizza(SimpleFactory simpleFactory) {

        this.simpleFactory = simpleFactory;
        orderType = getType();
        pizza = this.simpleFactory.createPizza(orderType);

        if (pizza != null) {
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } else {
            System.out.println("error ...");
        }

    }
}

// 写一个方法,可以获取客户希望订购的披萨种类
private Enum getType() {
    try {
        BufferedReader strIn = new BufferedReader(new InputStreamReader(System.in));
        System.out.println("input pizza 种类:");
        String str = strIn.readLine();
        return PizzaType.get(str);
    } catch (IOException e) {
        e.printStackTrace();
        return PizzaType.errorType;
    }
}

public enum PizzaType {

    cheeseType("cheeseType"),
    greekType("greekType"),
    pepperType("pepperType"),
    errorType("");

    private String pizzaType;
    PizzaType(String pizzaType) {
        this.pizzaType = pizzaType;
    }

    public String getName() {
        return pizzaType;
    }


    private static final Map<String,PizzaType> ENUM_MAP;
    static {
        Map<String, PizzaType> map = new ConcurrentHashMap<>();
        for (PizzaType instance : PizzaType.values()) {
            map.put(instance.getName(), instance);
        }
        ENUM_MAP = Collections.unmodifiableMap(map);
    }

    public static PizzaType get(String pizzaTypeStr) {
        PizzaType pizzaType = ENUM_MAP.get(pizzaTypeStr);
        return pizzaType != null ? pizzaType : PizzaType.errorType;
    }

}

更进一步

我们在具体创建 Pizza 对象是,每更换一个 Pizza 对象都需要修改客户端代码中静态工厂方法的参数,客户端代码将要重新编译,这对于客户端而言,违反了“开闭原则”,有没有一种方法能够在不修改客户端代码的前提下更换具体产品对象呢?

我们可以将静态工厂方法的参数存储在XML或properties格式的配置文件中

<?xml version="1.0"?>
    <config>
    <chartType>histogram</chartType>
    </config>

再通过一个工具类XMLUtil来读取配置文件中的字符串参数,这样客户端就不需要进行任何修改了。

小结

主要优点:

  • 工厂类包含必要的判断逻辑,可以决定在什么时候创建哪一个产品类的实例,客户端可以免除直接创建产品对象的职责,而仅仅“消费”产品,简单工厂模式实现了对象创建和使用的分离。
  • 客户端无须知道所创建的具体产品类的类名,只需要知道具体产品类所对应的参数即可,对于一些复杂的类名,通过简单工厂模式可以在一定程度减少使用者的记忆量。
  • 通过引入配置文件,可以在不修改任何客户端代码的情况下更换和增加新的具体产品类,在一定程度上提高了系统的灵活性。

主要缺点:

  • 由于工厂类集中了所有产品的创建逻辑,职责过重,一旦不能正常工作,整个系统都要受到影响。
  • 使用简单工厂模式势必会增加系统中类的个数(引入了新的工厂类),增加了系统的复杂度和理解难度。
  • 系统扩展困难,一旦添加新产品就不得不修改工厂逻辑,在产品类型较多时,有可能造成工厂逻辑过于复杂,不利于系统的扩展和维护。
  • 简单工厂模式由于使用了静态工厂方法,造成工厂角色无法形成基于继承的等级结构。

适用场景:

  • 工厂类负责创建的对象比较少,由于创建的对象较少,不会造成工厂方法中的业务逻辑太过复杂。
  • 客户端只知道传入工厂类的参数,对于如何创建对象并不关心。