Skip to content

工厂模式

crazysunj edited this page Apr 26, 2017 · 1 revision

       当我们要创建一个对象的时候,第一反应就是new一个啊,恩,没有错,错在对象老是在变。

       因此接口编程就这么诞生了,它可以隔离掉以后系统可能发生的一大堆改变。简单解释一下,如果代码是针对接口而写,那么通过多态,它可以与任何新类实现该接口。但是,当代码使用大量的具体类时,等于自找麻烦,因为一旦加入新的具体类,就必须改变代码。也就是说,你的代码并非“对修改关闭”。想用新的具体类型来扩展代码,必须重新打开它。

现在我们一步步来认识它。

故事的起源是,你有一个比萨店,而你是比萨店的主人。

那么你要做一个比萨的方法就是:

public Pizza orderPizza() {
    Pizza pizza = new Pizza();
    pizza.prepare();
    pizza.bake();
    pizza.cut();
    pizza.box();
    return pizza;
}

当然上面的只是抽象的比萨制作,如果我有多种类型呢?

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

这样做貌似不错,但是这些类型不能一直卖的好,那么可能会加几种类型,也有可能会删减几种类型,由此看来,我们又得去改方法里面的具体代码,但是改着改着,我们会发现,我们改的都是同一个地方,是的,具体的pizza不停的变,而其它的都没变。

聪明的你肯定已经想到了,提出来,搞个静态方法不就好了?

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

       作为一名优秀的程序员,总是存在好奇,探索精神,咋看一下,有解决问题么?不就是把问题从一个地方转到另一个地方么?

       这是不对的,假如以后其它类也用到这个方法呢?在修改的时候,不用一个个类改,只要改这么一个地方就行了。

       这里有点打脸哈,上面写着搞个静态方法,但是代码却不是,静态的叫静态工厂,它有个缺点就是不能通过继承来改变创建方式。

       重新回顾一下,我是比萨店的老板,我开了比萨店,而这是抽象的,应该有许多工厂去创建他们不同的比萨,更确切的说,我拥有的是一个品牌。

程序员还是用代码说话,一步一步来。

public class PizzaStore {
    private SimplePizzaFactory factory;
    public PizzaStore(SimplePizzaFactory factory) {
        this.factory = factory;
    }
    public Pizza orderPizza(String type) {
        Pizza pizza = factory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }
}

但是我想说,这并不是工厂模式,更确切的说谁是一种编程习惯。 问题总是不断哈,我们的品牌越做越大,总是并免不了加盟这玩意,作为老板,那么你必须严格细查这些加盟店的质量,加盟店可能来自不同的地区,风味不同,不妨说纽约、芝加哥、加州。

第一种想法就是,建立3个不同的工厂,传入PizzaStore即可。但是这种感觉是,3个厂加工了比萨之后,只能放在我的店里卖的节奏哈?

那我们为什么不把制造pizza抽象出来呢,同时店也抽象出来,不同的店去做不同类型的pizza,不是更好么?但都是在我品牌下。

看代码:

public abstract class PizzaStore {
    public Pizza orderPizza(String type) {
        Pizza 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 CheesePizza();
        } else if (type.equals("greek")) {
            return new GreekPizza();
        } else if (type.equals("pepperoni")) {
            return new PepperoniPizza();
        }
        return null;
    }
}

好像越来越有工厂模式的味道了,简单解析何谓工厂,例如这句代码

abstract Product factoryMethod(String type)
  • abstract:工厂方法是抽象的,所以依赖子类来处理对象的创建
  • Product:工厂方法必须返回一个产品。超类中定义的方法,通常使用到工厂方法的返回值
  • factoryMethod:工厂方法将客户(也就是超类中的代码,例如orderPizza())和实际创建具体产品的代码分隔开来
  • type:工厂方法可能需要参数(也可能不需要)来指定所要的产品

细心的朋友会发现一个问题哈,既然是纽约风味的,店是纽约的,为何卖的pizza却是抽象的,因此我们得修改具体产品

public abstract class Pizza {
    protected String name;//名称
    protected String dough;//面团
    protected String sauce;//调味汁
    protected List<String> toppings = new ArrayList<>();//配料
    public void prepare() {
    }
    public void bake() {
    }
    public void cut() {
    }
    public void box() {
    }
}

       抽象类Pizza提供名称、面团、调味汁、配料的属性,子类可以在构造方法中赋值,这样就打造了不同的口味,更友好一点,可以在prepare()或者构造函数等地方赋值默认值,然后在我们上面NYPizzaStore中,返回不同的纽约风味pizza。

       工厂模式已经清晰了,所有工厂模式都用来封装对象的创建,它通过让子类决定该创建的对象是什么,来达到将对象创建的过程封装的目的。

来我们来看看工厂模式的类图。

关于ProductFactory可以抽象也可以默认,都是允许的,没有太固定,变得只是形式,思想是不会变的。

       这里我要介绍一个原则,相信了解过原则的已经猜出来了,就是大名鼎鼎的依赖倒置原则—要依赖抽象,不要依赖具体类,这个原则说明了高层组件不能依赖底层组件,而且不管高层或底层组件,都应该依赖于抽象。

       还是没理解?让我们再回顾一下上面的例子,PizzaStore就是高层组件,而Pizza就是底层组件,而且前者依赖后者,很清晰。那么,要符合这个原则,我们应该怎么做呢?现在我们来倒置你的思考方式:

  1. 开始我们需要一家比萨店
  2. 我们得先从顶端开始,然后往下到具体类。但是,正如你所看到的你不想让比萨店例会这些具体类,要不然比萨店将全都依赖这些具体类。现在,开始”倒置”,别从顶端开始,而是从比萨开始,然后想想看能抽象化些什么
  3. 你得抽象抽象化一个比萨
  4. 你还得用一个工厂来将这些具体类取出比萨店,这样,各种不同的具体比萨类就只能依赖一个抽象,而比萨店也会依赖这个抽象,倒置完毕

以下方法能帮你避免在OO设计中违反依赖倒置原则

  • 变量不可以持有具体类的引用
  • 不要让类派生自具体类
  • 不要覆盖类中已实现的方法

具体说为什么这样能避免,经过上面的例子,我相信你已经领悟到了。

是时候该结尾了,理一理要点:

  • 所有的工厂都是用来封装对象的创建
  • 简单工厂,虽然不是真正的设计模式,但仍不失为一个简单的方法,可以将客户程序从具体类解耦
  • 工厂方法使用继承:把对象的创建委托给子类,子类实现工厂方法来创建对象
  • 抽象工厂使用对象组合:对象的创建被实现在工厂接口所暴露出来的方法中
  • 所有工厂模式都通过减少应用程序和具体类之间的依赖促进松耦合
  • 工厂方法允许类将实例化延迟到子类进行
  • 抽象工厂创建相关的对象家族,而不需要依赖它们的具体类
  • 依赖倒置原则,知道我们避免依赖具体类型,而要尽量依赖抽象
  • 工厂是很有威力的技巧,帮助我们针对抽象编程,而不要针对具体类编程

       最后,留一个小问题,我们在抽象类Pizza中加了这么多的属性,而这些属性的依赖,我们应该怎么设计呢?作为一名优秀的程序员,应该是不断的遇坑,不断探索,不断的发现,加油吧!