# 工厂模式

# 现实案例

  • 假如我们有一个披萨店,通过一个 Pizza 类我们可以创建披萨;
  • 同时在 Pizza 类之下,我们还可以创建很多不同的子类(芝士披萨,火腿披萨,牛肉披萨,等);
  • Pizza 类中有一个 orderPizza 方法可以用来制作披萨;
  • 我们希望这个方法可以支持制作各种种类的披萨;

# 方法一:❌ 用 if/else 实现种类判断

Pizza orderPizza(String type) {
  Pizza pizza = null;
  // 根据输入的字符串,判断披萨的类型
  // 然后再实例化具体的类
  if(type.equals("cheese")) {
    pizza = new CheesePizza();
  } else if(type.equals("greek")) {
    pizza = new GreekPizza();
  } else if(type.equals("pepperoni")) {
    pizza = new PepperoniPizza();
  }

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

  return pizza;
}

# 方法一的问题

  • 通过 if/else 来判断输入的字符串分辨类型;
  • 这种方法没有对修改封闭。只要新种类的披萨加入,我们就要改 orderPizza 方法的代码;

# 方法二:✅ 用 “简单工厂” 来改写

  • “工厂” 用来处理创建对象的细节
  • 简单工厂把创建实例对象的过程抽离出来,放到一个新的类中;
  • 上面的 orderPizza 方法中,一堆 if/else 判断就是创建实例的具体过程;
  • 我们把它抽离出来,放到一个新的 SimplePizzaFactory 类中:
  • SimplePizzaFactory 只做一件事情,就是帮用户创建披萨;
public class SimplePizzaFactory {
  public Pizza createPizza(String type) {
    Pizza pizza = null;

    if(type.equals("cheese")) {
      pizza = new CheesePizza();
    } else if(type.equals("greek")) {
      pizza = new GreekPizza();
    } else if(type.equals("pepperoni")) {
      pizza = new PepperoniPizza();
    }

    return pizza;
  }
}
  • 之后在 PizzaStore 类中有一个 SimplePizzaFactory 类型的实例属性,作为简单工厂的引用;
  • 通过简单工厂的 createPizza 方法就可以创建想要类型的披萨了;
public class PizzaStore {
  SimplePizzaFactory factory;

  public PizzaStore(SimplePizzaFactory factory) {
    this.factory = faoctory;
  }

  public Pizza orderPizza(String type) {
    Pizza pizza = null;
    pizza = factory.createPizza(type);
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();

  return pizza;
  }
}
  • 通过把创建披萨实例的过程提取出来,我们就可以复用这段代码了;
  • 不光 orderPizza 方法可以用,以后在别的方法中也可以用;
  • “简单工厂” 并不是一种设计模式,只是一种编程习惯

2020-2-19-17-36-11.png

# 工厂方法模式

  • 所有工厂模式都用来封装对象的创建
  • 工厂方法模式在 Creator 超类中定义了一个创建对象的接口,由子类去实现创建对象的具体过程;
  • 这个创建对象的接口,被称为 “工厂方法”;
  • 在 Creator 超类中定义的其他方法,都可以去调用这个工厂方法。但是在编写创建者类时,我们并不需要知道最后实际创建出来的实例是哪一个子类的;
  • 工厂方法让子类最后决定创建出来的 “实例” 的类型;

2020-2-19-18-2-29.png

  • 通过工厂方法模式,我们让产品的 “实现” 从 “使用” 中解耦。即使增加或修改产品,Creator 也不会受影响,只需要增加或修改 ConcreteProduct 类就行了;
  • 在 “简单工厂” 中,创建实例的是一个工厂对象。而在 “工厂方法模式” 中,超类定义了工厂方法接口,创建实例的是具体的子类;
  • 超类中的工厂方法接口,不一定非是抽象的接口。也可以定义成一个默认的方法。这样即使创建者没有任何子类,也可以创建产品;

# 方法三:✅ 用工厂方法模式改写

  • 先定义一个 PizzaStore 抽象类,在里面定义出抽象方法 createPizza。这个方法用来实例化披萨,但具体实例化的过程需要子类去自己实现;
public abstract class PizzaStore {
    // 在 Creator 超类中定义的其他方法,都可以去调用这个工厂方法。
    // 在编写创建者类时,我们并不需要知道最后实际创建出来的实例是哪一个子类
    Pizza orderPizza(String type) {
        Pizza pizza = null;

        pizza = createPizza(type);

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

    protected abstract Pizza createPizza(String type);
}
  • 然后编写 PizzaStore 的子类:
public class NYPizzaStore extends PizzaStore {

    @Override
    protected Pizza createPizza(String type) {
        if (type.equals("cheese")) {
            return new NYCheesePizza();
        }
        return null;
    }
}

public class ChicagoPizzaStore extends PizzaStore {
    @Override
    protected Pizza createPizza(String type) {
        if (type.equals("cheese")) {
            return new ChicagoCheesePizza();
        }
        return null;
    }
}
  • 在这里我就不写 Pizza 类的实现了;
  • 之后定披萨的话,我们需要先创建一个披萨店子类实例;
  • 然后通过这个子类实例去创建披萨;
PizzaStore nyPizzaStore = new NYPizzaStore();
Pizza pizza = nyPizzaStore.orderPizza("cheese");

2020-2-19-20-29-41.png

# 依赖倒置

  • 在我们没有使用工厂模式之前,我们通过披萨店类来创建所有的披萨实例;
  • 对披萨具体实现的任何改变都会影响到 PizzaStore。我们说 PizzaStore 依赖于披萨的实现;
  • 每增加一个新的披萨种类,就等于 PizzaStore 多了一个依赖;

2020-2-19-20-42-45.png

  • 我们需要减少代码对于具体类的依赖
  • 依赖倒置原则要依赖抽象,不要依赖具体类
  • 这个原则说明了:不能让高层组件依赖底层组件。而且,不管高层或底层组件,两者都应该依赖于抽象
    • 高层组件,是指由其他底层组件定义其行为的类;
    • 例如:PizzaStore 就是个高层组件,它的行为由披萨定义;
    • 而各种 Pizza 类就是底层组件;
    • 通过上面 👆 的类图,可以看出,高层组件 PizzaStore 依赖具体的披萨类;
    • 我们需要让代码,依赖抽象类,而不是依赖具体类。对于底层组件和高层组件都是如此;

2020-2-19-21-27-26.png

  • 很容易想到,我们把这堆披萨类,可以抽象出一个 Pizza 超类;
  • 然后让 PizzaStore 只依赖这个 Pizza 超类;
  • 同时所有的披萨子类,都是 Pizza 超类的具体实现;
  • 这样就使得高层组件和底层组件都依赖于 Pizza 抽象了;

# 依赖倒置思考方式

  • 设计代码时,不要先想 “我要如何创建所有的具体类”;
  • 应该先想 “所有的具体类可以抽象成什么东西”
  • 例如 🌰:
    • 不要先想披萨店如何创建所有的披萨实例;
    • 应该先想所有的披萨可以抽象成一个 Pizza 超类;
    • 然后我们让披萨店,用一个工厂方法去创建具体的披萨实例;
    • 这样 “披萨店” 和 “各种披萨” 只需要依赖 Pizza 超类就行了;
  • 下面是一些指导方针来避免违反依赖倒置原则:
    • 变量不可以持有具体类的引用。如果使用 new,就会持有具体类的引用,你可以改成工厂来避免;
    • 不要让类派生自具体类,如果派生自具体类,你就会依赖具体类,请派生自抽象(接口或者抽象类)
    • 不要覆盖基类中已经实现的方法,如果覆盖,那么说明基类就不是一个真正适合被继承的抽象,基类中已经实现的方法,应该有所有的子类共享;
  • 要尽量达到这些原则,但不是随时都要遵守;
上次更新: 8/22/2020, 8:14:09 AM