设计模式之工厂模式

“工厂”这种设计模式可能是最广为人知的模式之一了。其看起来最为简单,读起来也是郎朗上口。每次面试,让应聘者讲几种设计模式,往往都是“工厂模式,单例模式,代理模式...”,可真正能讲清楚“工厂”模式的人却少之又少。本篇文章,同样结合网络和自己理解,试着阐述我眼中的“工厂”。

通常所说的“工厂模式”大部分人最直接的理解可能是有个工厂类负责创建各个不同的实例。而实际上,这可能是最简单的工厂了,学名其实就叫“简单工厂”,除此以外,还有看起来更为牛x的“工厂方法”以及“抽象工厂”。下面一一讲开。

  1. 简单工厂

假设我们需要做一个简单的计算器,我们定义计算抽象类Operation,它有两个参数,numA和numB,有一个抽象方法getResult,返回计算的结果。另外有它的具体实现类,加法OperationAdd和减法OperationSub。工厂类OperationFactory根据入参返回具体的Operation。客户端类Client调用工厂类获取具体的运算方法进行计算,最终得到计算结果。

简单工厂.png

下面上代码:

/**
 * 功能简述: 运算符抽象类,输入两个整数,返回结果
 *
 * @author 
 */
public abstract class Operation {

    protected int numA;

    protected int numB;

    public abstract int getResult();
}
/**
 * 功能简述: 加法实现类
 *
 * @author
 */
public class OperationAdd extends Operation {

    @Override
    public int getResult() {
        return numA + numB;
    }
}
/**
 * 功能简述: 减法实现类
 *
 * @author
 */
public class OperationSub extends Operation {

    @Override
    public int getResult() {
        return numA - numB;
    }
}
/**
 * 功能简述: 简单工厂类
 *
 * @author
 */
public class OperationFactory {
    public static Operation createOperate(String operate) {
        Operation oper = null;
        switch (operate) {
            case "+":
                oper = new OperationAdd();
                break;
            case "-":
                oper = new OperationSub();
                break;
        }
        return oper;
    }
}
/**
 * 功能简述: 客户端调用
 *
 * @author 
 */
public class Client {

    public static void main(String[] args) {
        //输出3
        Operation oper = OperationFactory.createOperate("+");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
        //-----------------------------------------------------------------------
        //输出-1
        oper = OperationFactory.createOperate("-");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
    }
}

上面的代码运行无问题。也正是简单工厂的实现方法。我在第一篇提到简单工厂的问题,关于“开闭原则”(移步设计模式之开篇)。这里把相同的问题同样记下来,省掉你移步的麻烦。

现在我们需要增加乘法的实现类,我们需要怎么做?首先,需要写一个OperationMul继承Operation,实现父类中的getResult方法,返回乘积;其次,需要在工厂类OperationFactory中新增一段case语句,当case"*"号时返回OperationMul实现;最后,在client方法中测试实现。

/**
 * 功能简述: 乘法实现类
 *
 * @author 
 */
public class OperationMul extends Operation {

    @Override
    public int getResult() {
        return numA * numB;
    }
}
/**
 * 功能简述: 简单工厂类,改造,新增了对*的处理
 *
 * @author
 */
public class OperationFactory {
    public static Operation createOperate(String operate) {
        Operation oper = null;
        switch (operate) {
            case "+":
                oper = new OperationAdd();
                break;
            case "-":
                oper = new OperationSub();
                break;
            case "*":
                oper = new OperationMul();
                break;
        }
        return oper;
    }
}
/**
 * 功能简述: 客户端调用
 *
 * @author 
 */
public class Client {

    public static void main(String[] args) {
        //输出3
        Operation oper = OperationFactory.createOperate("+");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
        //-----------------------------------------------------------------------
        //输出-1
        oper = OperationFactory.createOperate("-");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
        //-----------------------------------------------------------------------
        //输出2
        oper = OperationFactory.createOperate("*");
        if (null != oper) {
            oper.numA = 1;
            oper.numB = 2;
            System.out.println(oper.getResult());
        }
    }
}

到目前为止,上面的代码运行无问题,大部分人也是这么做的。好,假设又来需求了,我们需要增加除法的实现类?需要怎么做?我们不仅需要增加除法的具体实现类,同样需要修改工厂类。这里有什么问题?我们不断的在新增类,同时又修改类!要求做单元测试的同学们可能会有感触了,测试同学同样会有感触吧?因为修改了一个类,这个类的UT覆盖率下降,同时有没有引入其他问题?测试同学会想,你修改了原来类的代码,我是不是需要回归全部场景?上面的实例代码是非常简单的,如在实际开发中可能业务会复杂的多,修改一个类你得小心再小心。在设计模式中,这叫违背了“开闭原则”!那么我们要怎么办,“工厂方法”可以解决我们的烦恼。

  1. 工厂方法

工厂方法模式去掉了简单工厂中工厂方法的静态属性,并且抽象了工厂,也就是具体为加法还是减法工厂的压力由工厂子类来分担,没有一个无敌的工厂类负责创建各类具体实现,也就避免了业务调整时需要调整这个无敌类。工厂方法的类图:

工厂方法.png

下面上具体代码:

/**
 * 功能简述: 运算符抽象类,输入两个整数,返回结果
 *
 * @author 
 */
public abstract class Operation {

    protected int numA;

    protected int numB;

    public abstract int getResult();
}
/**
 * 功能简述: 加法实现类
 *
 * @author
 */
public class OperationAdd extends Operation {

    @Override
    public int getResult() {
        return numA + numB;
    }
}

这一部分没有变化,抽象计算方法,具体计算方法。变化在下面这个部分:

/**
 * 功能简述: 抽象工厂类,此类只定义接口,不定义实现,此为和简单工厂的区别之一
 *
 * @author
 */
public interface OperationFactory {

    Operation createOperate();
}
/**
 * 功能简述: 加法工厂类
 *
 * @author
 */
public class OperationAddFactory implements OperationFactory {

    @Override
    public Operation createOperate() {
        return new OperationAdd();
    }
}
/**
 * 功能简述: 客户端调用
 *
 * @author 
 */
public class Client {

    public static void main(String[] args) {
        OperationAddFactory addFactory = new OperationAddFactory();
        Operation oper = addFactory.createOperate();
        oper.numA = 1;
        oper.numB = 2;
        //输出3
        System.out.println(oper.getResult());
    }
}

为了避免过多展示不需要的代码,在这个例子中稍微简化了下,移除了减法的实现。下面我们需要新增减法的实现,我们要怎么做?

工厂方法.png

我们新增了运算具体实现类OperationSub,新增了减法工厂OperationSubFactory,然后就可以进行测试验证了。在这种实现中,我们只是新增代码,没有修改代码!

/**
 * 功能简述: 减法实现类
 *
 * @author 
 */
public class OperationSub extends Operation {

    @Override
    public int getResult() {
        return numA - numB;
    }
}
/**
 * 功能简述:减法工厂类
 *
 * @author
 */
public class OperationSubFactory implements OperationFactory {

    @Override
    public Operation createOperate() {
        return new OperationSub();
    }
}
/**
 * 功能简述: 测试实现
 *
 * @author
 */
public class Client {

    /**
     * 功能描述: <br>
     *
     * @param args
     */
    public static void main(String[] args) {
        OperationAddFactory addFactory = new OperationAddFactory();
        Operation oper = addFactory.createOperate();
        oper.numA = 1;
        oper.numB = 2;
        //输出3
        System.out.println(oper.getResult());

        OperationSubFactory subFactory = new OperationSubFactory();
        oper = subFactory.createOperate();
        oper.numA = 1;
        oper.numB = 2;
        //输出-1
        System.out.println(oper.getResult());
    }
}

看到这里,可能你已经有一些感觉了。

  1. 抽象工厂

抽象工厂模式是工厂方法模式的升级版本,它用来创建一组相关或者相互依赖的对象。

抽象工厂比较难以理解,主要是不知道解决哪一类问题,而且看起来和工厂方法非常类似,工厂子类不仅创建一个具体产品实现,更可能会创建多个。想要清楚的理解抽象工厂就需要从它的起源说起。

抽象工厂模式的起源或者最早的应用,是用于创建分属于不同操作系统的视窗构建。比如:命令按键(Button)与文字框(Text)都是视窗构建,在UNIX操作系统的视窗环境和Windows操作系统的视窗环境中,这两个构建有不同的本地实现,它们的细节有所不同。

在每一个操作系统中,都有一个视窗构建组成的构建家族。在这里就是Button和Text组成的产品族。而每一个视窗构件都构成自己的等级结构,由一个抽象角色给出抽象的功能描述,而由具体子类给出不同操作系统下的具体实现。

产品类.png

可以发现在上面的产品类图中,有两个产品的等级结构,分别是Button等级结构和Text等级结构。同时有两个产品族,也就是UNIX产品族和Windows产品族。UNIX产品族由UNIX Button和UNIX Text产品构成;而Windows产品族由Windows Button和Windows Text产品构成。

产品族.png

显然,一个系统只能够在某一个操作系统的视窗环境下运行,而不能同时在不同的操作系统上运行。所以,系统实际上只能消费属于同一个产品族的产品。

用上面的例子实现的类图及代码如下:

抽象工厂.png
/**
 * 功能简述: 按钮接口<br>
 *
 */
public interface Button {

}
/**
 * 功能简述: unix系统按钮实现类<br>
 *
 */
public class UnixButton implements Button {

    public UnixButton() {
        System.out.println("I am unix button!");
    }
}
/**
 * 功能简述: windows系统按钮实现类<br>
 *
 */
public class WindowsButton implements Button {

    public WindowsButton() {
        System.out.println("I am windows button!");
    }
}
/**
 * 功能简述: 文本框接口<br>
 *
 */
public interface Text {

}
/**
 * 功能简述: unix系统文本框实现类<br>
 *
 */
public class UnixText implements Text {

    public UnixText() {
        System.out.println("I am unix text!");
    }
}
/**
 * 功能简述: windows系统文本框实现类<br>
 *
 */
public class WindowsText implements Text {

    public WindowsText() {
        System.out.println("I am windows text!");
    }
}

上面定义了两个接口,分别为Button和Text,表示两类抽象产品,另外还有每个接口对应Unix和Windows的两个实现。

/**
 * 功能简述: 抽象工厂接口<br>
 *
 */
public interface AbstractFactory {

    /**
     * 
     * 功能描述: 创建按钮工厂<br>
     *
     * @return
     */
    public Button createButton();

    /**
     * 
     * 功能描述: 创建文本工厂<br>
     *
     * @return
     */
    public Text createText();
}

抽象工厂类和简单工厂不一致的地方在于其定义了两个方法,返回同一产品族的Button和Text。下面定义Unix和Windows不同的实现:

/**
 * 功能简述: unix系统实现<br>
 *
 */
public class UnixSystem implements AbstractFactory {

    @Override
    public Button createButton() {
        return new UnixButton();
    }

    @Override
    public Text createText() {
        return new UnixText();
    }
}
/**
 * 功能简述: windows系统实现<br>
 *
 */
public class WindowsSystem implements AbstractFactory {

    @Override
    public Button createButton() {
        return new WindowsButton();
    }

    @Override
    public Text createText() {
        return new WindowsText();
    }
}

可以看到,Unix实现类返回Unix的UnixButton和UnixText,Windows实现类返回Windows的WindowsButton和WindowsText。每类工厂都有两个以上的具体产品,形成一个产品族。

/**
 * 功能简述: 测试类<br>
 *
 */
public class Client {

    /**
     * 功能描述: 测试方法<br>
     *
     * @param args
     */
    public static void main(String[] args) {
        UnixSystem unixSys = new UnixSystem();
        unixSys.createButton();
        unixSys.createText();

        WindowsSystem winSys = new WindowsSystem();
        winSys.createButton();
        winSys.createText();
    }

    // 运行结果
    // I am unix button!
    // I am unix text!
    // I am windows button!
    // I am windows text!
}

通过以上的几个例子,已经讲完了“工厂”设计模式。

当然实际开发中,总有不同的实现,你能看出是“工厂”模式的影子,但仔细对比发现却又不是“工厂”的样子。我觉得,设计模式只是提供的一个标准的模板,是一个标杆,每个不同的实现围绕标杆即可。设计模式的最终目的是为了解耦,便于软件的维护。在实际开发中使用设计模式固然是好事,但也不能为了设计模式而设计,我们应当多尝试,时间久了就会自然而然的在软件设计中使用模式,使得软件质量提高。希望大家不断进步。

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

推荐阅读更多精彩内容

  • 工厂模式是最常用的一类创建型设计模式,之前一直以为工厂模式只是23中设计模式中的一种,重新了解才知道这个模式还要细...
    晨鸣code阅读 1,272评论 0 6
  • 一、简单工厂模式 简单工厂模式又称静态工厂方法模式。它存在的目的很简单:将对象的创建过程封装进一个类。 先来看看它...
    innovatorCL阅读 436评论 0 1
  • 1. 工厂方法模式 使用场景 工厂方法模式是new一个对象的替代品,所以在所有需要生成对象的地方都可以使用,到那时...
    niaoge2016阅读 337评论 0 1
  • 设计模式中主要分为三大类:创建型、结构型、行为型 工厂模式属于创建型,顾名思义,创建型模式关注对象的创建过程,它将...
    嘿嘿_小于同学阅读 266评论 0 3
  • 使用优盘为盒子安装应用软件,是盒子安装第三方软件的最简单的方法,但不是唯一的方法。不过在使用安装方法之前,需先去网...
    ckyyouknow阅读 249评论 2 3