设计模式(四)工厂模式

我们在第一次学习对象这个概念的时候,新建对象用的是这样的代码:

Product product = new Product();

但是真正到了工程上这样的代码是“生硬”的,当遇到 Product 改变,或者 Product 初始化流程更改这样的新需求时,要改变的代码量相当的大。我们需要在找出所有调用 Product 构造器的地方,并为之添加上新的代码,这样的代码设计显然是不符合设计原则的。

为了改善上面的情况,工厂模式应运而生。

工厂模式是一种创建模式。创建模式对类的实例化过程进行了抽象,能将类的创建和使用分离。工厂类将产品类的初始化封装在方法之中,对于外界而言,只要知道产品类的公共接口,就可以正常实现功能,这使得各个模块开发时只专注于自己的业务逻辑,忽略对象的创建细节。

由于场景不同,我们可以看到不同形态的工厂模式,一般可以分为以下几种:

    1. 简单工厂模式
    1. 工厂方法模式
    1. 抽象工厂模式

三种模式间并没有绝对的优劣,因为各个模式各有优缺,需要根据特定的场景来选择。但是大多数开发场景中,我们并没有这样庞大的系统,因此,通常我们使用的还是前两种方法。

一、简单工厂模式

简单工厂模式是一种类创建模式,通常情况下,简单工厂的对象会有一个静态方法用于创建产品,并且这个方法通常根据传入的参数来生产指定的产品。这种工厂模式即使你不知道有设计模式这一说法,也会自行使用,因为它构造简单,实现方便,也确实能在项目中实现立竿见影的效果。

1)简单工厂模型

SimpleFactory.png

先来看看最简单的实现代码:

public class SimpleFactory {
    
    public static Product createProduct(String arg){
        Product product = null;
        switch (arg) {
            case "A":
                product = new ProductA();
                
                // init something
                
                break;
            case "B":
                product = new ProductB();

                // init something
                
                break;
            default:
                throw new RuntimeException("product not exist for arg: "+arg);
        }
        return product;
    }
    
}

2)使用场景
对于产品种类少,且产品相对固定,出现新种类产品的可能性小的时候,可以使用简单工厂模式

我们假定一个简单的场景,依旧用上一篇建造者模式中提到的电脑。

现在电脑都可以办公和学习,因此,我们定义一个公共接口:

public interface IComputer {

    void work();

    void study();
    
}

然后,现在一个人有一台电脑用来工作或者学习,但是这台电脑是有配置高低分别的,这时候我们需要两个类实现上面的接口,并且在构造器里,分别配置它的硬件信息:

public class ComputerA implements IComputer {
    private static final String TAG = "ComputerA";

    public ComputerA(){
        //初始化高端硬件配置
        Log.i(TAG, "ComputerA: 初始化高端硬件配置");
    }

    @Override
    public void work() {
        //高端机工作
        Log.i(TAG, "ComputerA: 高端机工作");
    }

    @Override
    public void study() {
        //高端机玩学习
        Log.i(TAG, "ComputerA: 高端机玩学习");
    }
}

public class ComputerB implements IComputer {
    private static final String TAG = "ComputerB";
    
    public ComputerB(){
        //初始化普通硬件配置
        Log.i(TAG, "ComputerA: 初始化普通硬件配置");
    }

    @Override
    public void work() {
        //普通机工作
        Log.i(TAG, "ComputerA: 普通机工作");
    }

    @Override
    public void study() {
        //普通机玩学习
        Log.i(TAG, "ComputerA: 普通机玩学习");
    }
}

通过构造器,我们已经可以获取到不同硬件配置的电脑,然后我们需要对不同电脑安装适合的工作学习软件:

public class SimpleFactory {

    public static IComputer createProduct(String arg){
        IComputer product = null;
        switch (arg) {
            case "A":
                product = new ComputerA();
                // 下载学习软件 M
                // 下载工作软件 N
                // init something

                break;
            case "B":
                product = new ComputerB();
                // 下载学习软件 X
                // 下载工作软件 Y
                // init something

                break;
            default:
                throw new RuntimeException("product not exist for arg: "+arg);
        }
        return product;
    }

}

下面在想要工作学习的地方,我们只需要传入所需电脑对应的名称,就可以获取我们需要的电脑并且实现我们工作学习的目的:

IComputer iComputerA = SimpleFactory.createProduct("A");
iComputerA.study();
iComputerA.work();

IComputer iComputerB = SimpleFactory.createProduct("B");
iComputerB.study();
iComputerB.work();

下面是这个过程的日志图:


效果图-g.png

对比代码和日志,我们可以看到,由于简单工厂的封装,在需要使用电脑工作学习的时候,我们只要知道当前需要的电脑名称:“A” 或 “B”,就可以获取我们的电脑,并且通过统一的接口方法实现工作学习的目的。

这样做,显然使得代码变得更加简洁,对于业务而言逻辑也变得清楚,因为无关的东西都被隐藏了起来。

3)优缺点
**优点:
构造容易、逻辑简单。只要通过一个工厂方法就可以得到需要的对象,不用关注这个对象是如何构造出来。工厂模式能略过了产品的初始化细节,并且规范统一了所有产品功能的调用方法。

**缺点:
由于简单工厂所有产品的初始化方法都在一个方法中,当产品数量过多且初始化过程复杂的时候,代码会显得过长且难以理解。同时,通过标志位分辨生产产品的类型,使得代码内部耦合严重,如果需要新添加一个产品 C ,就必须要修改这个工厂类对象,这显然不符合前面提到的 开放封闭原则(ASD) ,即对拓展开放,对修改封闭。优秀的设计应该能够在有新产品的时候,尽量保持原代码不变,只添加新代码就能实现功能。这一点,简单工厂模式无法达到。

那么这一点,我们需要怎样达到呢?于是,工厂方法模式出现了。

二、工厂方法模式

工厂方法模式也是一种类创建模式,与简单工厂不同的是,它不再通过参数来区分当前生产的产品对象。工厂方法模式将工厂抽象化,定义了一个公共父类用于规定创建产品对象的公共接口,并把所有产品实力化的时间延迟到工厂的子类。文字说明有些不好理解,先来看一下图解。

MethodFactory.png

由图可以看到,对应产品 A 有指定的 FactoryA 用于生产,对应产品 B 有指定的 FactoryB 来生产。FactoryA 和 FactoryB 都是 IFactory 的子类,如果想要生产新的产品 C ,只需要多加一个 IFactory 子类用于生产 C ,而不需要改动原有代码。因此这种模式,也叫做多态工厂模式。

还是用上面的电脑例子,现在我们要多加一个生产工厂,生产低端电脑:

MethodFactory2.png

工厂方法模式遵循[开放封闭原则(ASD)],但是它也有缺点,即完成相同的功能,对于工厂方法而言要新增一个工厂类一个产品类,文件数量多,使得系统变得复杂。

而现在问题又来了,高端、中端、低端电脑都有了,但是实际上,对于电脑我们不只有配置高低,还有品牌。现在高端、中端、低端电脑提供商有两家,一家是联想,一家是宏基。在这样的情况下, 工厂方法模式的设计并没有办法让我们获取我们需要的品牌的电脑。于是,我们用到了抽象工厂模式。

三、抽象工厂模式

抽象工厂模式是对工厂模式的抽象。事实上,抽象工厂模式是最具有一般性质的工厂模式。为了了解这个最为抽象的模式,首先,我们需要了解几个概念,我们还是会以熟悉的电脑为例子进行说明:

  • **产品继承结构:例如高端电脑是一个类别,而联想高端电脑和宏基高端电脑,都是这个类别的子类,实现了它的功能接口。
  • **产品族:产品族是指由同一个工厂生产的,位于不同产品等级结构中的一组产品。这里是指同个品牌下的一系列产品,例如联想有高端、中端、低端的电脑产生流水线,同样的宏基也有,而这高端、中端、低端的三种电脑,就是他们的产品族

在这里,例子中的高端、中端和低端更换为商务本、游戏本和二合一的平板笔记本可能更合适些。不过,管他呢。

一个抽象工厂模式一般包含如下角色:
AbstractFactory:抽象工厂
ConcreteFactory:具体工厂
AbstractProduct:抽象产品
Product:具体产品

先看下图解:


AbstractFactory.png

可以看到,不同品牌的生产工厂分别实现了抽象工厂的接口,将自己的生产过程封装起来,在我们需要电脑工作学习的时候,只需要根据需要的品牌选取合适的工厂类,然后利用工厂类选择合适的机型,就可以获取我们需要的电脑对象:

public interface IComputerFactory {
    IComputer createComputerA();
    IComputer createComputerB();
}

public class LenovoComputerFactory implements IComputerFactory {
    private static final String TAG = "LenovoComputerFactory";

    public LenovoComputerFactory(){
        Log.i(TAG, "LenovoComputerFactory: 联想电脑工厂初始化");
    }
    @Override
    public IComputer createComputerA() {
        Log.i(TAG, "createComputerA: 联想电脑工厂制造高级配置电脑");
        return new ComputerA();
    }

    @Override
    public IComputer createComputerB() {
        Log.i(TAG, "createComputerB: 联想电脑工厂制造普通配置电脑");
        return new ComputerB();
    }
}

调用日志打印:


效果图-g.png

抽象工厂模式的优点在于,新添加产品族是符合[开放封闭原则(ASD)],但是新增产品的继承结构却是不符合的,在具体项目中需要权衡倾向,再作应用。

感谢:

  1. android_interview - builder_pattern
  2. UML类图与类的详解

以上。

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

推荐阅读更多精彩内容