设计模式之工厂模式

工厂模式

当使用 new 时,我们是在实例化一个具体类,用的是实现,而不是接口。代码绑定着具体类会导致代码更脆弱,更缺乏弹性。

封装创建对象的代码,放到一个“工厂”里。工厂处理创建对象的细节。

松耦合的OO设计。

简单工厂模式

定义

简单工厂其实不是一个设计模式,反而比较像一种变成习惯。

例子

  • 工厂类
/**
 * 简单工厂类,负责创建对象
 */
public class SimplePizzaFactory {
    public Pizza createPizza(String type){
        Pizza pizza = null;
        switch (type){
            case "cheese":
                pizza = new CheesePizza();
                break;
            case "pepperoni":
                pizza = new Pepperonipizza();
                break;
            case "clam":
                pizza = new ClamPizza();
                break;
            case "veggie":
                pizza = new VeggiePizza();
                break;
        }
        return pizza;
    }
}
  • 使用类
/**
 * 披萨店
 * 调用工厂生产披萨
 */
public class PizzaStore {
    SimplePizzaFactory factory;

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

    /**
     * 传入订单类型来使用工厂创建披萨
     * @param type
     * @return
     */
    public Pizza orderPizza(String type){
        Pizza pizza;
        pizza = factory.createPizza(type);
        pizza.prepare();
        pizza.bake();
        pizza.cut();
        pizza.box();
        return pizza;
    }

    //其他方法
}

类图

角色:

  1. 工厂类角色:这是本模式的核心,含有一定的商业逻辑和判断逻辑,用来创建产品
  2. 抽象产品角色:它一般是具体产品继承的父类或者实现的接口。
  3. 具体产品角色:工厂类所创建的对象就是此角色的实例。在Java中由一个具体类实现。

分析

下面我们从开闭原则(对扩展开放;对修改封闭)上来分析下简单工厂模式,每增加一种新的披萨,需要在工厂类中增加相应的创建业务逻辑(switch 中添加 case),违背开闭原则。

静态工厂

利用静态方法定义一个简单的工厂,这是很常见的,称为静态工厂。为何这样用?因为不需要使用创建对象的方法来实例化对象。但是也有缺点,不能通过继承来改变创建方法的行为。

工厂方法模式

定义

定义一个用于创建对象的接口,让子类决定实例化哪一个类。工厂方法模式让一个类的实例化延迟到其子类。

工厂方法用来处理对象的创建,并将这样的行为封装在子类中,这样,客户程序中关于超类的代码就和子类对象创建代码解耦了。

结构

例子

  • 创建者(Creator)类
/**
 * 抽象的披萨店
 */
public abstract class PizzaStore {
    /**
     * 传入订单类型来使用工厂方法创建披萨
     * @param type
     * @return
     */
    public Pizza orderPizza(String type){
        Pizza pizza;
        //创建
        pizza = createPizza(type);
//        pizza.prepare();
//        pizza.bake();
//        pizza.cut();
//        pizza.box();
        return pizza;
    }

    //实例化披萨的责任被移到一个“方法”中,此方法就如同是一个“工厂”
    abstract Pizza createPizza(String type);

    //其他方法
}
  • 具体创建者
/**
 * 加盟店 纽约店创建当地风味披萨
 * 返回Pizza由子类全权负责实例化哪一个具体Pizza
 */
public class NYPizzaStore extends PizzaStore {
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        switch (type){
            case "cheese":
                pizza = new NYStyleCheesePizza();
                break;
            case "pepperoni":
                pizza = new NYStylePepperoniPizza();
                break;
            case "clam":
                pizza = new NYStyleClamPizza();
                break;
            case "veggie":
                pizza = new NYStyleVeggiePizza();
                break;
        }
        return pizza;
    }
}
  • 产品类
/**
 * 产品类
 */
public abstract class Pizza {
    String name;
    String dough;
    String sauce;
    ArrayList toppings = new ArrayList();

    void prepare(){
        System.out.println("Preparing" + name);
        System.out.println("Tossing dough...");
        System.out.println("Adding sauce...");
        System.out.println("Adding toppings:");
        for (int i = 0; i < toppings.size(); i++){
            System.out.println("    " + toppings.get(i));
        }
    }

    void bake(){
        System.out.println("Baking...");
    }

    void cut(){
        System.out.println("Cutting...");
    }

    void box(){
        System.out.println("Boxing...");
    }

    public String getName() {
        return name;
    }
}
  • 具体产品
/**
 * 抽象类Pizza的具体子类
 */
public class NYStyleCheesePizza extends Pizza {
    public NYStyleCheesePizza() {
        name = "NY Style Cheese Pizza";
        dough = "Thin Crust Dough";
        sauce = "Marinara Sauce";
        toppings.add("Grated Reggiano Cheese");
    }
}
  • 使用
  PizzaStore nyStore = new NYPizzaStore();
  Pizza pizza = nyStore.orderPizza("cheese");
  System.out.println("your ordered a " + pizza.getName() + "\n");

类图

  • 创建者类

  • 产品类

分析

参与角色

  1. Product(document)定义工厂方法所创建对象的接口。
  2. ConcreteProduct(mydocument)实现 product 接口。
  3. Creator(application)声明工厂方法,可以调用工厂方法以创建一个product对象
  4. ConcreteCreator (MyApplication)重新定义工厂方法,以返回一个ConcreteProduct实例

总结

工厂方法模式将产品的“实现”从“使用”中解耦,如果增加产品或改变产品的实现,Creator 不会受到影响,因为 Creator 与任何ConcreteProduct 之间都不是紧耦合。

简单工厂和工厂方法的区别:

工厂方法的具体创建者类看起来像是简单工厂。简单工厂把全部的事情,在一个地方都处理完了,然而工厂方法却是创建一个框架,让子类决定要如何实现。简单工厂不具备工厂方法的弹性,因为简单工厂不能变更正在创建的产品。

抽象工厂模式

定义

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

例子

披萨店要统一原料,由一个原料工厂来提供各种原料:

  • 工厂接口

    public interface PizzaIngredientFactory{
    
        public Dough createDough();
        public Sauce createSauce();
        public Cheese creatrCheese();
        ...     
    }
    
  • 为每一个区域建造一个工厂,如:纽约原料工厂:

    public class NYPizzaIngredientFactory implements PizzaIngredientFactory{
        public Dough createDough(){
            return new THinCrustDough();
        }
    
        public Sauce createSauce(){
            return new MarinaraSauce();
        }
    
        ...
    }
    
  • 重做披萨,使用工厂生产出来的原料

/**
 * 产品类
 */
public abstract class Pizza {
    String name;
    //一组原料
    Dough dough;
    Sauce sauce;
    Veggies veggies[];
    Cheese cheese;
    ...

    //抽象方法,收集制作披萨的原料,来自原料工厂
    abstract void prepare();

    void bake(){
        System.out.println("Baking...");
    }

    void cut(){
        System.out.println("Cutting...");
    }

    void box(){
        System.out.println("Boxing...");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  • 具体产品类 调用原料工厂
public class CheesePizza extends Pizza {
    PizzaIngredientFactory indgredientFactory;

    //构造函数传入原料工厂
    public CheesePizza(PizzaIngredientFactory indgredientFactory;){
        this.indgredientFactory = indgredientFactory;    
    }

    void prepare(){
        System.out.println("preparing" + name);
        indgredientFactory.createDough();
        indgredientFactory.createSauce();
        indgredientFactory.createCheese();
    }
}
  • 具体创建者类 再调用原料工厂
public class NYPizzaStore extends PizzaStore {
    @Override
    Pizza createPizza(String type) {
        Pizza pizza = null;
        NYPizzaIngredientFactory ingredientFactory = new NYPizzaIngredientFactory();
        switch (type){
            case "cheese":
                pizza = new NYStyleCheesePizza(ingredientFactory);
        pizza.setName("cheese");
                break;
            case "pepperoni":
                pizza = new NYStylePepperoniPizza(ingredientFactory);
        pizza.setName("pepperoni");
                break;
            case "clam":
                pizza = new NYStyleClamPizza(ingredientFactory);
        pizza.setName("clam");
                break;
            case "veggie":
                pizza = new NYStyleVeggiePizza(ingredientFactory);
        pizza.setName("veggie");
                break;
        }
        return pizza;
    }
}
  • 过程
//创建一个具体生产者的实例 一个纽约披萨店
PizzaStore nyPizzaStore = new NYPizzaStore();
//接受订单
nyPizzaStore.orderPizza("cheese");
//orderPizza 调用 createPizza方法
Pizza pizza = createPizza("cheese");
//涉及原料工厂
Pizza pizza = new CheesePizza(nyIngredientFactory);
//接下来要准备披萨,一旦调用 prepare方法,工厂将被要求准备原料
void prepare(){
    dough = factory.createDough();
    sauce = factory.createSauce();
    ...
}

总结

抽象工厂允许客户使用抽象的接口来创建一组相关的产品,而不需要知道(或关心)实际产出的具体产品是什么。这样一来,客户就从具体的产品中被解耦。

抽象工厂的方法经常以工厂方法的方式实现。抽象工厂的任务是定义一个负责创建一组产品的接口,这个接口内的每个方法都负责创建一个具体产品,同时我们利用实现抽象工厂的子类来提供这些具体的做法。

抽象工厂与工厂方法的区别

工厂方法:创建对象用的方法时继承。使用工厂方法创建一个对象,需要扩张一个类,并覆盖它的工厂方法。

抽象工厂:创建对象通过对象的组合。提供一个用来创建一个产品家族的抽象类型,这个类型的子类定义列产品被产生的方法。

设计原则

  • 依赖倒置原则: 要依赖抽象,不要依赖具体类。

    跟针对接口编程,不针对实现编程有点类似,然而这里更强调“抽象”。这个原则说明了:不能让高层组件依赖底层组件,而且,不管高层组件或底层组件,两者都应该依赖于抽象。

    所谓高层组件,是由其他底层组件定义其行为的类,例如 PizzaStore 是高层组件,因为它的行为是由披萨定义的: PizzaStore 创建不同的披萨对象。而披萨本身属于底层组件。

    例如工厂方法中,底层组件具体具体产品类(具体披萨类)依赖于高层的抽象(Pizza类),同样的高层组件也依赖于相同的抽象(抽象PizazStore依赖Pizza抽象)。

下面的指导方针,能帮你避免在OO设计中违反依赖倒置原则:

  1. 变量不可以持有具体类的引用(用 new 就会持有具体类的引用,可以用工厂模式避开)。
  2. 不要让类派生自具体类(如果派生自具体类,就会依赖于具体类。请派生自一个抽象(接口或抽象类))。
  3. 不要覆盖基类中的方法(如果覆盖,那么基类就不是一个适合被继承的抽象,基类中实现的方法,应该由子类共享)。