《Head First 设计模式》读书笔记(4)——工厂模式 Factory

全文共5245个词,阅读需要15分钟,本项目相关代码地址被托管在 gitee
前面我们一直在使用new操作符,但是实例化这种行为并不应该总是公开的进行,而且初始化经常会造成耦合问题,工厂模式将摆脱这种复杂的依赖,本次内容包括简单工厂,工厂方法和抽象工厂三种情况。

每次在代码中使用new的时候都是在针对实现编程,可是我们应该针对接口编程

Duck duck;
if(a){
    duck=new Duck1();
}else{
    duck=new Duck2();
}

上面的代码是很难维护的,具体要实例化什么类编译过程不能确定,维护修改很容易犯错。

从技术上看,new的使用是绝对没有问题的,但是实际情况是
1.如果你针对接口编程,你可以利用多态,实现该接口并没有太大问题
2.但是如果代码中使用了很多具体类,一旦加入新的类,就必须改变代码,这就违反了开放-关闭原则。

栗子

背景:假设你有一家 pizza 店,你有很多种 pizza,

第一种思路,直接最普通的方式

你可能就会写下这样的代码:

public class PizzaStore {
    Pizza orderPizza(String type) {
        Pizza pizza = null;

        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("clam")) {
            pizza = new ClamPizza();
        } else if (type.equals("veggie")) {
            pizza = new VeggiePizza();
        }

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

但是如果新上市一种pizza或者下架一种pizza,你就需要修改这段代码,这样就没有做到对修改关闭
于是我们也找到变化的代码和不变的代码,现在就可以进行封装了

第二种思路将变化的部分单独写成对象,这个对象就是简单工厂

定义一个工厂,为所有pizza封装创建对象的代码

public class SimpleFactory {
    public Pizza createPizza(String type) {
        Pizza pizza = null;
        if (type.equals("cheese")) {
            pizza = new CheesePizza();
        } else if (type.equals("clam")) {
            pizza = new ClamPizza();
        } else if (type.equals("veggie")) {
            pizza = new VeggiePizza();
        }
        return pizza;
    }
}

public class PizzaStore {
    SimpleFactory simpleFactory;

    public PizzaStore(SimpleFactory simpleFactory) {
        this.simpleFactory = simpleFactory;
    }

    Pizza orderPizza(String type) {
        Pizza pizza = null;

        pizza = simpleFactory.createPizza(type);

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

这么做的好处看起来好像只是把原来的代码放进到另外一个类中,但是当以后出现变化时,自需要修改这个类,就可以,并且这样做的好处还在于可能现在只有客人点餐才会用到这段代码,如果可以点外卖的话,仍然可以用这段代码,代码得到了复用

上面的简单工厂并不是一个真正的模式,只是一种编程习惯,这个不能算工厂模式,不过也算是基本满足需求

第三种思路——工厂方法模式

背景更新:假如现在你要开分店,各种加盟商进来后,他们都要开发符合本地口味的pizza,那就需要各个地方都有一个工厂,也就是每个地方继承SimpleFactory类,但是每个工厂并不是完全使用你原来的烘培方法

public abstract class PizzaStore {
    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);
}


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;
    }
}

现在订购这些订单时,自需要实例化各自风格的工厂,就算都是芝士披萨,也是来自不同商店口味的。
工厂方法模式类图如下:
产品类类图

创建者类类图

总结:

  • 工厂方法模式定义了一个创建对象的接口,但由子类决定要实例化的是哪一个,工厂方法让类把实例化推迟到子类,工厂方法模式的优点在于帮助产品从使用中分离,从使用中解耦。
  • 和简单工厂的区别,两者类似,但是在于createPizza方法,工厂方法让每个上篇自行负责,而简单工厂使用的则是PizzaStore对象。
  • 简单工厂把所有的事情在一个地方干完了,然而工厂方法则是写了一个框架,让子类具体实现,PizzaStore作为工厂的orderPizza方法提供了一个一般的框架,以便创建披萨,其具体依赖工厂的方法来创建具体的披萨。
  • 两者都实现了不让创建对象的代码到处乱跑。

看看对象依赖情况,原来的PizzaStore依赖于所有的Pizza对象,当这些对象发生改变时,可能会影响到PizzaStore,PizzaStore时依赖具体类的,PizzaStore就是高层组件,Pizza各种对象就是低层组件,但是设计原则有一条是:依赖抽象,不要依赖具体类
而使用工厂方法之后就不在出现这种依赖很多具体类的情况了

用依赖倒置原则重构代码

下面是一些指导方针来避免违反依赖倒置原则:

  • 变量不可以持有具体类的引用。如果使用new,就会持有具体类的引用,你可以改成工厂来避免
  • 不要让类派生自具体类,如果派生自具体类,你就会依赖具体类,请派生自抽象(接口或者抽象类)
  • 不要覆盖基类中已经实现的方法,如果覆盖,那么基类说明就不是一个真正适合被继承的抽象,基类中已经实现的方法,应该有所有的子类共享

要尽量达到这些原则,但不是随时都要遵守

第四种——抽象工厂模式

现在背景有变,有些加盟店,使用低价原料来增加利润,你必须采取一些手段,以免回调你的披萨店平牌。
你打算建造一家成产原料的工厂,并将原料运送到各家加盟店,那么剩下最后一个问题,不同的区域原料是不一样的,对于两个加盟店给出了两组不同的原料:

建造原料工厂
我们要建造一个工厂来生产原料,这个工厂负责创建原料家族中的每一种原料。

public interface PizzaIngredientFactory {
    public Dough createDough();

    public Sauce createSauce();

    public Cheese createCheese();

    public Veggies[] createVeggies();

    public Pepperoni createPepperoni();

    public Clams createClams();

}

要做的事情是:
1.为每个区域建造一个工厂,你需要创建一个继承自PizzaIngredientFactory的子类来实现每一个创建方法。
2.实现一组原料类供工厂使用,例如RegianoCheese,RedPeppers,ThickCrustDough.这些类可以在何时的区域间共享。
3.然后你仍然需要将这一切组织起来,将新的原料工厂整合进旧的PizzaStore代码中。

创建纽约的原料工厂

class NYPizzaIngredientFactory implements PizzaIngredientFactory{

    @Override
    public Dough createDough() {
        return null;
    }

    @Override
    public Sauce createSauce() {
        return null;
    }

    @Override
    public Cheese createCheese() {
        return null;
    }

    @Override
    public Veggies[] createVeggies() {
        return new Veggies[0];
    }

    @Override
    public Pepperoni createPepperoni() {
        return null;
    }

    @Override
    public Clams createClams() {
        return null;
    }
}

重做披萨

abstract class Pizza{
    String name;
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    Pepperoni pepperoni;
    Clams clams;
    abstract void prepare();
    void cut(){
        System.out.println("Cutting the pizza into diagonal slices");
    }
    void box(){
        System.out.println("Place pizza in official PizzaStore box");
    }
    void setName(String name){
        this.name=name;
    }
    public String getName(){
        return name;
    }
    public void toString2(){
        //这里打印披萨的代码
    }
}

继续重做披萨

class CheesePizza extends Pizza{
    PizzaIngredientFactory ingredientFactory;
    public CheesePizza(PizzaIngredientFactory ingredientFactory){
        this.ingredientFactory=ingredientFactory;
    }
    @Override
    void prepare() {
        System.out.println("Preparing"+name);
        dough=ingredientFactory.createDough();
        sauce=ingredientFactory.createSauce();
        cheese=ingredientFactory.createCheese();
    }
}

再回到披萨店

class NYPizzaStore extends PizzaStore{
   protected Pizza createPizza(String item){
       Pizza pizza=null;
       PizzaIngredientFactory ingredientFactory=new NYPizzaIngredientFactory();
       if(item.equals("cheese")){
           pizza=new CheesePizza(ingredientFactory);
           pizza.setName("New ……");
       }else if(item.equals("veggie")){
           //……
       }
       return pizza;
   }
}

我们做了些什么?
我们引入新类型的工厂,也就是所谓的抽象工厂,来创建披萨原料家族。
通过抽象工厂所提供的接口可以创建产品的家族,利用这个接口书写代码,我们的代码将从实际工厂解耦,以便在不同上下文中实现各式各样的工厂,制造出各种不同的产品。

定义抽象工厂模式
抽象工厂模式提供一个接口,用于创建相关或依赖对象的家族,而不需要明确指定具体类。
抽象工厂模式类图:

这是一张更复杂的类图,让我们从PizzaStore的观点来看一看它:

工厂方法和抽象工厂

抽象工厂的每个方法都实际上看起来像工厂方法,方法声明成抽象,子类方法去覆盖这些方法去创建对象。
抽象工厂的任务是定义一个负责创造一组产品的接口,每个方法就是创建一种产品,这就是工厂方法,所以在抽象工厂中利用工厂方法来实现生产方法是自然的。

对比:
两者都是用来创建对象,但是工厂方法使用的是继承,而抽象工厂使用的是对象的组合。
比如工厂方法创建pizza是各种PizzaStore继承后覆盖createPizza()方法实现的,让客户从具体类型中解耦。
但是对于抽象工厂,提供了一个用来创建一个产品家族的抽象类型,这个类型的子类定义了产品被产生的方法,要想使用这个工厂必须实例化它,然后将它传入一些针对抽象类型所写的代码中。
比如PizzaIngredientFactory这个接口定义了一堆原料的做法,而对于纽约的原料加工厂先实现这些接口,然后纽约的商店实例化这个原料加工厂,就保证了不同的商店使用到各自不同的原料,把客户从所使用的实际产品中解耦。
一群还是一个,一群产品集合用抽象工厂,具体哪些类中可以确定一个抽象类,子类继承实现使用工厂方法
工厂方法图:
工厂方法
抽象工厂图:
抽象工厂

发表评论

电子邮件地址不会被公开。