工厂模式

135 阅读6分钟

1. 概述

工厂模式,就是将对象的创建交给一个工厂,能够更加便利地管理对象的创建。工厂模式分为三种,简单工厂模式、工厂方法模式、抽象工厂模式,我们通过一个例子来逐步进行了解。

2. 三种工厂模式

案例:我们需要实现一个披萨店的类,店里卖两种口味的pizza,有芝士cheese口味的,和胡椒pepper口味的。根据用户输入的类型,是cheese还是pepper,然后制造出相应的pizza,过程中需要打印pizza的制作过程过程包括prepare、bake、cut、box。

注意:在这里披萨店是作为客户端

Pizza抽象类:

// 将Pizza做成抽象的
public abstract class Pizza {

    protected String name;

    // 不同的Pizza原材料不同,所以这个方法定义为抽象的,要到子类中由子类自己实现
    public abstract void prepare();

    public void bake() {
        System.out.println(name + " baking;");
    }

    public void cut() {
        System.out.println(name + " cutting;");
    }

    public void box() {
        System.out.println(name + " boxing;");
    }

    public void setName(String name) {
        this.name = name;
    }
}

不同类型的pizza需要继承Pizza类然后重写prepare方法,因为不同类型的pizza的材料是不同的

2.1 传统方式

传统方式,直接在Pizza店中制作pizza,根据用户需要的Pizza类型,直接制作出相应的Pizza。

传统方式的UML图为: PizzaStore的代码实现:

public class PizzaStore {
    // 订购pizza
    public OrderPizza() {
        Pizza pizza = null;
        String orderType; // 订购pizza的类型
        Scanner scanner = new Scanner(System.in);
        do {
            System.out.println("请输入pizza的类型");
            orderType = scanner.nextLine();
            if (orderType.equals("greek")) {
                pizza = new GreekPizza();
                pizza.setName("希腊披萨");
            } else if (orderType.equals("chees")) {
                pizza = new CheesPizza();
                pizza.setName("芝士披萨");
            } else if (orderType.equals("pepper")) {
                pizza = new PepperPizza();
                pizza.setName("辣椒披萨");
            } else {
                break;
            }
            pizza.prepare();
            pizza.bake();
            pizza.cut();
            pizza.box();
        } while (true);
    }
}

代码应该很容易理解

分析优缺点:

  1. 很好理解,简单易操作
  2. 缺点是违背了OCP原则,也就是对扩展开放,对修改关系。因为当我们需要增加新的类型的pizza的时候,那么我们对PizzaStore类中的orderPizza方法进行修改,要增加一个else if判断对种类进行判断然后添加相应的代码
  3. 如果有多个地方有制作pizza的代码,那么就需要全部都进行修改

2.2 简单工厂模式

简单工厂模式,很好理解,也就是将制作pizza的过程直接交由一个工厂类来实现。

我们画出简单工厂模式的UML类图: 从UML图能够看出PizzaStore类直接使用PizzaFactory类的createPizza方法来获取Pizza对象,这就好像pizza店都直接从工厂拿货进行售卖

代码实现:

public class SimpleFactory {
    public Pizza createPizza(String orderType) {
        System.out.println("使用简单工厂模式");

        Pizza pizza = null;
        switch (orderType) {
            case "greek":
                pizza = new GreekPizza();
                pizza.setName("希腊披萨");
                break;
            case "cheese":
                pizza = new CheesePizza();
                pizza.setName("芝士披萨");
                break;
            case "pepper":
                pizza = new PepperPizza();
                pizza.setName("辣椒披萨");
                break;
        }
        return pizza;
    }

    // 简单工厂模式也叫作静态工厂模式
}

创建了一个简单工厂,实现了一个createPizza方法,能够根据需要的pizza类型来制作pizza 那么我们的PizzaStore类就修改如下:

public class PizzaStore {
    // 定义一个简单工厂对象
    private SimpleFactory simpleFactory;

    public void setSimpleFactory(SimpleFactory simpleFactory) {
        this.simpleFactory = simpleFactory;
    }

    public void orderPizza() {
        Scanner scanner = new Scanner(System.in);
        String pizzaType;
        Pizza pizza;
        do {
            System.out.println("输入pizza类型");
            pizzaType = scanner.nextLine();
            pizza = simpleFactory.createPizza(pizzaType);
            if (pizza != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            } else {
                System.out.println("订购失败");
                break;
            }
        }
        while (true);
    }
}

可以看到,PizzaStore类直接使用工厂类得到了一个Pizza对象,而自己不会再对pizza类型进行判断,直接告诉工厂我需要什么,让工厂进行判断从而生产

简单工厂相较于传统模式,将对象的创建交给一个工厂类来实现,当我增加了新的pizza种类,我不需要再修改PizzaStore的代码,我只需要修改工厂类的代码即可。这样当我有多个PizzaStore,也不需要修改多出,只需要修改一处即可。

当然也可以将工厂类的方法定义为静态的方法

2.3 工厂方法模式

现在我有了新的需求,我需要制作北京的芝士披萨,和北京的胡椒披萨,还有伦敦的芝士披萨,和伦敦的胡椒披萨。

首先我们仍然考虑使用简单工厂模式,那么就需要创建不同的简单工厂类,比如BJPizzaFactory、LDPizzaFactory。这样就需要两个工厂,为了简化,我们让两个工厂继承同一个工厂类,工厂类中定义一个抽象的创建对象方法,让子类去做具体的实现。这就变成了工厂方法模式

工厂方法模式的核心是将对象的实例化推迟到了子类

抽象工厂类的代码:

public abstract class PizzaFactory {
    // 定义一个抽象方法,createPizza,让各个工厂子类自己实现
    abstract Pizza createPizza(String pizzaType);

    // 构造器
    public void orderPizza() {
        Scanner scanner = new Scanner(System.in);
        String pizzaType;
        Pizza pizza;
        do {
            System.out.println("输入pizza类型");
            pizzaType = scanner.nextLine();
            // 直接调用,获得pizza
            pizza = createPizza(pizzaType);
            if (pizza != null) {
                pizza.prepare();
                pizza.bake();
                pizza.cut();
                pizza.box();
            } else {
                System.out.println("订购失败");
                break;
            }
        }
        while (true);
    }
}

实现类的代码:

public class BJPizzaFactory extends PizzaFactory {
    @Override
    Pizza createPizza(String pizzaType) {
        Pizza pizza = null;
        if (pizzaType.equals("cheese")) {
            pizza = new BJCheesePizza();
        } else if (pizzaType.equals("pepper")) {
            pizza = new BJPepperPizza();
        }
        return pizza;
    }
}
public class LDPizzaFactory extends PizzaFactory {
    @Override
    Pizza createPizza(String pizzaType) {
        Pizza pizza = null;
        if (pizzaType.equals("cheese")) {
            pizza = new LDCheesePizza();
        } else if (pizzaType.equals("pepper")) {
            pizza = new LDPepperPizza();
        }
        return pizza;
    }
}

然后store类直接调用工厂类的方法即可

2.4 抽象工厂模式

  1. 抽象工厂模式,定义了一个interface用于创建相关或有依赖关系的对象蔟,而无需指明具体的类
  2. 抽象工厂模式可以将简单工厂模式和工厂方法模式进行了整合
  3. 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者进一步的抽象)
  4. 将工厂抽象为两层,AbsFactory(抽象工厂)和具体实现的工厂子类,程序员可以根据创建对象类型使用对应的工厂子类,这样将单个的简单工厂变成了工厂簇,更利于代码的维护和扩展
  5. 类图: 抽象工厂模式的代码:
public interface AbsFactory {
     Pizza createPizza(String pizzaType);
}

然后子类实现接口

public class BJPizzaFactory implements AbsFactory {
    @Override
    public Pizza createPizza(String pizzaType) {
        Pizza pizza = null;
        if (pizzaType.equals("cheese")) {
            pizza = new BJCheesePizza();
        } else if (pizzaType.equals("pepper")) {
            pizza = new BJPepperPizza();
        }
        return pizza;
    }
}

然后PizzaStore的代码

public class PizzaStore {
    private AbsFactory factory;

    public void setFactory(AbsFactory absFactory) {
        this.factory = absFactory;
    }

    public void orderPizza() {
        Scanner scanner = new Scanner(System.in);
        String pizzaType;
        Pizza pizza;
        while (true) {
            System.out.println("请输入pizza类型");
            pizzaType = scanner.nextLine();
            pizza = factory.createPizza(pizzaType);
            if (pizza == null) {
                break;
            }
            pizza.prepare();
        }
    }
}

注意这里使用的factory对象是接口对象

3. Java源码中的工厂模式

Java中的Calendar类就是采用的工厂模式进行设计的。

private static Calendar createCalendar(TimeZone zone,
                                       Locale aLocale)
{
    CalendarProvider provider =
        LocaleProviderAdapter.getAdapter(CalendarProvider.class, aLocale)
                             .getCalendarProvider();
    if (provider != null) {
        try {
            return provider.getInstance(zone, aLocale);
        } catch (IllegalArgumentException iae) {
            // fall back to the default instantiation
        }
    }

    Calendar cal = null;

    if (aLocale.hasExtensions()) {
        String caltype = aLocale.getUnicodeLocaleType("ca");
        if (caltype != null) {
            switch (caltype) {
            case "buddhist":
            cal = new BuddhistCalendar(zone, aLocale);
                break;
            case "japanese":
                cal = new JapaneseImperialCalendar(zone, aLocale);
                break;
            case "gregory":
                cal = new GregorianCalendar(zone, aLocale);
                break;
            }
        }
    }
    if (cal == null) {
        // If no known calendar type is explicitly specified,
        // perform the traditional way to create a Calendar:
        // create a BuddhistCalendar for th_TH locale,
        // a JapaneseImperialCalendar for ja_JP_JP locale, or
        // a GregorianCalendar for any other locales.
        // NOTE: The language, country and variant strings are interned.
        if (aLocale.getLanguage() == "th" && aLocale.getCountry() == "TH") {
            cal = new BuddhistCalendar(zone, aLocale);
        } else if (aLocale.getVariant() == "JP" && aLocale.getLanguage() == "ja"
                   && aLocale.getCountry() == "JP") {
            cal = new JapaneseImperialCalendar(zone, aLocale);
        } else {
            cal = new GregorianCalendar(zone, aLocale);
        }
    }
    return cal;
}

这个方法根据不同的地区类型来创建不同的对象并返回

3. 小结

  1. 工厂模式的意义:将实例化对象的代码提取出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦,从而提高醒目的扩展和维护性
  2. 设计模式符合依赖抽象原则