设计模式 | 用实际案例详解工厂模式

我们来看一个具体需求
看一个披萨的项目,要便于披萨种类的扩展,要便于维护,有以下要求:

  • 披萨的种类很多(比如GreekPizz、CheesePizz等)
  • 披萨的制作有prepare,bake,cut,box
  • 完成披萨店订购功能

首先我们先编写一个Pizza抽象类:

package com.cxc.factory.simplefactory.pizzastore.pizza;

/**
 * 抽象Pizza类
 */
public abstract class Pizza {
    //Pizza名字
    protected String name;

    //准备原材料,不同的披萨不一样,因此我们做成抽象方法
    public abstract void preable();
    //切
    public void cut(){
        System.out.println("cut");
    }
    //烘烤
    public void bake(){
        System.out.println("bake");
    }
    //打包
    public void box(){
        System.out.println("box");
    }

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

然后编写不同的种类的披萨类去继承这个抽象类,并实现抽象方法:

package com.cxc.factory.simplefactory.pizzastore.pizza;

public class CheesePizza extends Pizza {
    @Override
    public void preable() {
        System.out.println("给制作奶酪披萨准备原材料");
    }
}
package com.cxc.factory.simplefactory.pizzastore.pizza;

public class GreekPizza extends Pizza {
    @Override
    public void preable() {
        System.out.println("给希腊披萨准备原材料");
    }
}

接下来是完成披萨订单类,在这里面根据输入的披萨种类的不同来新建不同的披萨对象:

package com.cxc.factory.simplefactory.pizzastore.order;

import com.cxc.factory.simplefactory.pizzastore.pizza.CheesePizza;
import com.cxc.factory.simplefactory.pizzastore.pizza.GreekPizza;
import com.cxc.factory.simplefactory.pizzastore.pizza.Pizza;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class OrderPizza {

    public OrderPizza() {
        Pizza pizza = null;
        String orderType;   //订购披萨的类型
        do{
            orderType = getType();
            if (orderType.equals("greek")){
                pizza = new GreekPizza();
            }else if (orderType.equals("cheese")){
                pizza = new CheesePizza();
            }else {
                break;
            }
            pizza.preable();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while (true);
    }

    private String getType(){
        try {
            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("input pizza type:");
            String str = strin.readLine();
            return str;
        }catch (Exception e){ }

        return "";
    }
}

披萨店订购:

package com.cxc.factory.simplefactory.pizzastore.order;

/**
 * 相当于一个客户端,发出订购
 */
public class PizzaStore {

    public static void main(String[] args) {
        new OrderPizza();
    }
}

上面实现的这个案例中存在问题,因为我们新建披萨的代码编写在披萨订单类里面,所以如果我们需要扩展一个新的披萨种类的时候,除了需要新建扩展类之外,还需要在此处改代码,违反了开闭原则。在这个案例中需要用到工厂模式来更改。

一、简单工厂模式

1、基本介绍

  • 简单工厂模式是属于创建型模式,是工厂模式的一种。简单工厂模式是由一个工厂对象决定创建出哪一种产品类的实例。简单工厂模式是工厂模式家族中最简单实用的模式。
  • 简单工厂模式:定义了一个创建对象的类,由这个类来封装实例化对象的行为
  • 在软件开发中,当我们会用到大量的创建某种、某类或某批对象时,就会使用到工厂模式。

那对于上面的案例我们如何使用简单工厂方法来更改呢?
首先我们需要将披萨订单类OrderPizza中生产披萨的代码抽取出来,形成一个简单工厂类:

package com.cxc.factory.simplefactory.pizzastore.order;

import com.cxc.factory.simplefactory.pizzastore.pizza.CheesePizza;
import com.cxc.factory.simplefactory.pizzastore.pizza.GreekPizza;
import com.cxc.factory.simplefactory.pizzastore.pizza.Pizza;

/**
 * 简单工厂模式
 */
public class SimpleFactory {

    /**
     * 根据orderType返回对应的Pizza对象
     * @param orderType
     * @return
     */
    public Pizza createPizza(String orderType){
        Pizza pizza = null;

        System.out.println("简单工厂模式");
        if (orderType.equals("greek")){
            pizza = new GreekPizza();
        }else if (orderType.equals("cheese")){
            pizza = new CheesePizza();
        }

        return pizza;
    }
}

披萨订单类OrderPizza1可以更改为如下:

package com.cxc.factory.simplefactory.pizzastore.order;

import com.cxc.factory.simplefactory.pizzastore.pizza.CheesePizza;
import com.cxc.factory.simplefactory.pizzastore.pizza.GreekPizza;
import com.cxc.factory.simplefactory.pizzastore.pizza.Pizza;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class OrderPizza1 {

    //定义一个简单工厂对象
    private SimpleFactory simpleFactory;

    Pizza pizza = null;

    public OrderPizza1(SimpleFactory simpleFactory) {
        setFactory(simpleFactory);
    }

    public void setFactory(SimpleFactory simpleFactory){
        String orderType = "";  //用户输入的
        this.simpleFactory = simpleFactory; //设置简单工厂对象

        do {
            orderType = getType();
            pizza = this.simpleFactory.createPizza(orderType);

            if (pizza != null){
                //订购成功
                pizza.preable();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else{
                System.out.println("订购披萨失败");
                break;
            }
        }while (true);
    }

    private String getType(){
        try {
            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("input pizza type:");
            String str = strin.readLine();
            return str;
        }catch (Exception e){ }

        return "";
    }
}

订购客户端:

package com.cxc.factory.simplefactory.pizzastore.order;

/**
 * 相当于一个客户端,发出订购
 */
public class PizzaStore {

    public static void main(String[] args) {
        //使用简单工厂模式
        new OrderPizza1(new SimpleFactory());
    }
}

这样改动后,如果我们需要扩展新的披萨种类,只需要在简单工厂类中更改代码,其它地方不需要进行更改。

接下来对于这个案例,还有一个新需求如下:
客户在点披萨时,可以点不同口味的披萨,比如北京的奶酪Pizza、北京的胡椒Pizza或者是伦敦是奶酪Pizza、伦敦的胡椒Pizza。

这个时候有两种思路:

  • 思路1:使用简单工厂模式,创建不同的简单工厂类,比如BJPizzaSimpleFactory、LDPizzaSImpleFactory等,但是考虑到项目的规模,以及软件的可维护性、可扩展性这个思路并不是很好。
  • 思路2:使用工厂方法模式。

二、工厂方法模式

1、基本介绍

工厂方法模式:定义了一个创建对象的抽象方法,由子类决定要实例化的类。工厂方法模式将对象的实例化推迟到子类

对于上述案例的新需求,我们使用工厂方法模式更改,OrderPizza是一个抽象的工厂类,其中的createPizza()方法为抽象方法,然后定义了不同地区口味的披萨工厂类:北京口味披萨工厂类(BJOrderPizza),伦敦口味披萨工厂类(LDOrderPizze),这两个披萨工厂类分别去生产各个地区的披萨,类图如下所示:



Pizza类:

package com.cxc.factory.factorymethod.pizzastore.pizza;

/**
 * 抽象Pizza类
 */
public abstract class Pizza {
    //Pizza名字
    protected String name;

    //准备原材料,不同的披萨不一样,因此我们做成抽象方法
    public abstract void preable();
    //切
    public void cut(){
        System.out.println("cut");
    }
    //烘烤
    public void bake(){
        System.out.println("bake");
    }
    //打包
    public void box(){
        System.out.println("box");
    }

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

BJCheesePizza类:

package com.cxc.factory.factorymethod.pizzastore.pizza;

public class BJCheesePizza extends Pizza {
    @Override
    public void preable() {
        System.out.println(" 北京的奶酪pizza准备原材料 ");
    }
}

BJPepperPizza类:

package com.cxc.factory.factorymethod.pizzastore.pizza;

public class BJPepperPizza extends Pizza {
    @Override
    public void preable() {
        System.out.println(" 北京的胡椒pizza准备原材料");
    }
}

LDCheesePizza类:

package com.cxc.factory.factorymethod.pizzastore.pizza;

public class LDCheesePizza extends Pizza {
    @Override
    public void preable() {
        System.out.println(" 伦敦的奶酪pizza准备原材料");
    }
}

LDPepperPizza类:

package com.cxc.factory.factorymethod.pizzastore.pizza;

public class LDPepperPizza extends Pizza {
    @Override
    public void preable() {
        System.out.println(" 伦敦的胡椒pizza准备原材料");
    }
}

抽象的工厂类OrderPizza:

package com.cxc.factory.factorymethod.pizzastore.order;

import com.cxc.factory.factorymethod.pizzastore.pizza.Pizza;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public abstract class OrderPizza {

    /**
     * 定义一个抽象的方法
     * @param orderType
     * @return
     */
    public abstract Pizza createPizze(String orderType);

    public OrderPizza() {
        Pizza pizza = null;
        String orderType;   //订购披萨的类型
        do{
            orderType = getType();
            //抽象的方法,由工厂子类完成
            pizza = createPizze(orderType);
            pizza.preable();
            pizza.bake();
            pizza.cut();
            pizza.box();
        }while (true);
    }

    private String getType(){
        try {
            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("input pizza type:");
            String str = strin.readLine();
            return str;
        }catch (Exception e){ }

        return "";
    }
}

两个工厂子类分别去实现OrderPizza类:

package com.cxc.factory.factorymethod.pizzastore.order;

import com.cxc.factory.factorymethod.pizzastore.pizza.*;

public class LDOrderPizza extends OrderPizza {
    @Override
    public Pizza createPizze(String orderType) {
        Pizza pizza = null;
        if (orderType.equals("cheese")){
            pizza = new LDCheesePizza();
        }else if (orderType.equals("pepper")){
            pizza = new LDPepperPizza();
        }
        return pizza;

    }
}
package com.cxc.factory.factorymethod.pizzastore.order;

import com.cxc.factory.factorymethod.pizzastore.pizza.BJCheesePizza;
import com.cxc.factory.factorymethod.pizzastore.pizza.BJPepperPizza;
import com.cxc.factory.factorymethod.pizzastore.pizza.Pizza;

public class BJOrderPizza extends OrderPizza {

    @Override
    public Pizza createPizze(String orderType) {
        Pizza pizza = null;
        if (orderType.equals("cheese")){
            pizza = new BJCheesePizza();
        }else if (orderType.equals("pepper")){
            pizza = new BJPepperPizza();
        }
        return pizza;
    }
}

披萨订购:

package com.cxc.factory.factorymethod.pizzastore.order;

public class PizzaStore {
    public static void main(String[] args) {

        //在此示例创建北京口味的各种披萨
        new BJOrderPizza();
    }
}

三、抽象工厂模式

1、基本介绍

  • 抽象工厂模式定义了一个接口(interface)用于创建相关或有依赖关系的对象簇,而无需指明具体的类
  • 抽象工厂模式可以将简单工厂模式工厂方法模式进行整合
  • 从设计层面看,抽象工厂模式就是对简单工厂模式的改进(或者称为进一步的抽象)
  • 将工厂抽象成两层,抽象工厂和具体实现的工厂子类。程序员可以根据创建对象类型使用对应的工厂子类。这样将单个的简单工厂类变成了工厂簇,更利于代码的维护和扩展

上面的披萨案例使用抽象工厂模式后类图如下所示:


接下来我们再来将其实现一下,抽象的工厂接口如下:

package com.cxc.factory.absfactory.pizzastore.order;
import com.cxc.factory.absfactory.pizzastore.pizza.Pizza;
/**
 * 抽象工厂模式的抽象层
 */
public interface AbsFactory {

    /**
     * 由下面的具体子类实现
     * @param orderType
     * @return
     */
    public Pizza createPizza(String orderType);
}

两个具体的工厂子类:

package com.cxc.factory.absfactory.pizzastore.order;

import com.cxc.factory.absfactory.pizzastore.pizza.BJCheesePizza;
import com.cxc.factory.absfactory.pizzastore.pizza.BJPepperPizza;
import com.cxc.factory.absfactory.pizzastore.pizza.Pizza;

/**
 * 具体的工厂子类
 */
public class BJFactory implements AbsFactory {
    @Override
    public Pizza createPizza(String orderType) {
        System.out.println("使用的是抽象工厂模式");
        Pizza pizza = null;
        if (orderType.equals("cheese")){
            pizza = new BJCheesePizza();
        }else if (orderType.equals("pepper")){
            pizza = new BJPepperPizza();
        }

        return pizza;
    }
}
package com.cxc.factory.absfactory.pizzastore.order;

import com.cxc.factory.absfactory.pizzastore.pizza.*;

public class LDFactory implements AbsFactory {
    @Override
    public Pizza createPizza(String orderType) {
        System.out.println("使用的是抽象工厂模式");
        Pizza pizza = null;
        if (orderType.equals("cheese")){
            pizza = new LDCheesePizza();
        }else if (orderType.equals("pepper")){
            pizza = new LDPepperPizza();
        }

        return pizza;
    }
}

披萨订单类:

package com.cxc.factory.absfactory.pizzastore.order;

import com.cxc.factory.absfactory.pizzastore.pizza.Pizza;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class OrderPizza {

    AbsFactory absFactory;

    public OrderPizza(AbsFactory absFactory){
        this.setAbsFactory(absFactory);
    }

    private void setAbsFactory(AbsFactory factory){
        Pizza pizza = null;
        String orderType = "";//用户输入
        this.absFactory = factory;

        do{
            orderType = getType();
            pizza = factory.createPizza(orderType);
            if (pizza != null){
                pizza.preable();
                pizza.bake();
                pizza.cut();
                pizza.box();
            }else{
                System.out.println("订购失败");
                break;
            }
        }while (true);
    }

    private String getType(){
        try {
            BufferedReader strin = new BufferedReader(new InputStreamReader(System.in));
            System.out.println("input pizza type:");
            String str = strin.readLine();
            return str;
        }catch (Exception e){ }

        return "";
    }
}

披萨商店类:

package com.cxc.factory.absfactory.pizzastore.order;

public class PizzaStore {

    public static void main(String[] args) {
        new OrderPizza(new BJFactory());  //这里演示北京的工厂子类
    }
}

四、JDK源码分析之工厂模式

Calendar是一个简单工厂模式。
例如下面这个创建Calendar实例的方法:

Calendar calendar = Calendar.getInstance();

getInstance方法的实现:

    /**
     * Gets a calendar using the default time zone and locale. The
     * <code>Calendar</code> returned is based on the current time
     * in the default time zone with the default
     * {@link Locale.Category#FORMAT FORMAT} locale.
     *
     * @return a Calendar.
     */
    public static Calendar getInstance()
    {
        return createCalendar(TimeZone.getDefault(), Locale.getDefault(Locale.Category.FORMAT));
    }

这里返回的createCalendar方法点进去:

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

可以看到在创建实例的时候是根据你传入的参数不同而来创建不同的实例的,这是一个简单工厂模式。

五、工厂模式小结

1、工厂模式的意义

将实例化对象的代码抽象出来,放到一个类中统一管理和维护,达到和主项目的依赖关系的解耦,从而提高项目的扩展和维护性。

2、三种工厂模式

  • 简单工厂模式
  • 工厂方法模式
  • 抽象工厂模式

3、设计模式的依赖抽象原则

  • 创建对象实例时,不要直接new类,而是把这个new类的动作放在一个工厂的方法中,并返回。也可说为:变量不要直接持有具体类的引用。
  • 不要让类继承具体类,而是继承抽象类或者实现interface(接口)。
  • 不要覆盖基类中已经实现的方法。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,185评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,445评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,684评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,564评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,681评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,874评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,025评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,761评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,217评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,545评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,694评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,351评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,988评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,778评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,007评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,427评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,580评论 2 349