创建型设计模式-抽象工厂模式

模式介绍

前段时间介绍了工厂方法模式
本次介绍的模式非常容易和工厂方法模式混淆,并且复杂度也有一定的提升,叫做抽象工厂模式
其实在现实生活中,工厂都会生产某一样具体的产品,不存在说抽象的工厂。
那么该模式的含义到底是什么呢?
抽象工厂模式的出现最早是为了解决不同操作系统下的图形化处理,这就好比iOS中的Button、Android中的Button、WindowPhone中的Button一样,虽然都是Button,但系统的不同,会导致Button也存在差异。
这就引出了抽象工厂模式的定义:
为创建出一组相互依赖的对象提供一个封装接口。
定义中有几个关键字,需要注意:

  • 一组:这是和工厂方法模式的重要区别所在,抽象工厂模式用于生产多个产品,而不是一个。如果生产一个产品,请使用工厂方法模式建造者模式
  • 相互依赖:生产出一组产品之后,这组产品一定是相互依赖的,注意是一定。就好比上面的例子中,Android系统能使用iOS系统的Button吗?答案是否定的,因为iOS的Button依赖于iOS系统。

解释完名词,想必大家对抽象工厂模式的使用场景就有了一些认知:
当产品很多,并且有特定的关联可以进行抽象,就可以使用抽象工厂模式

模式构成

抽象工厂模式的主要角色还是4个:

  • AbstractFactory:抽象工厂,包含一组产品的生产抽象,每一个方法都应对应一个产品
  • ConcreteFactory:抽象工厂实现,应包含生产一组产品的具体实现。
  • AbstractProduct:一组产品中的一个产品抽象。
  • ConcreteProduct:具体的产品实现。

模式示例

现在我们开始实现在上述介绍中提到的例子。
我们先来将思路捋一捋:


抽象工厂模式-对应关系
  1. 我们产品包含:操作系统、Button。
  2. 工厂应该生产一组产品,即生产操作系统和Button。
  3. 生产出来的一组产品应相互依赖,并且和另一组产品相互独立。

接下来,我们就按照思路来实现抽象工厂模式
首先我们来初始化抽象产品AbstractProduct
操作系统System:

public interface SystemAbsProduct {

    //获取系统型号
    String getSystem();

}

Button:

public interface ButtonAbsProduct {

    //按钮响应
    String getButtonName(SystemAbsProduct system);

}

我们可以发现,Button与操作系统存在依赖关系,这种依赖关系的体现由开发人员自己来控制,我这里只是个简单实例。
两个产品的抽象已经有了,我们先不急着去实现它们。
接下来我们来定义抽象工厂AbstractFactory,它应该有两个方法,分别返回系统和Button两个产品:

public interface AbsFactory {

    //创建操作系统
    SystemAbsProduct createSystem();

    //创建Button
    ButtonAbsProduct createButton();
}

到这里应该都没有什么问题,抽象工厂、抽象产品都已经创建完成了。
接下来我们想要分别构建出WindowPhone系统、Android系统、iOS系统下的Button。
我们应该能想到,必须要先要有具体的产品,才能用具体工厂来生产。
所以接下来,我们要先创建具体的产品:
操作系统

public interface SystemAbsProduct {

    //获取系统型号
    String getSystem();

    public class WindowPhone implements SystemAbsProduct{

        @Override
        public String getSystem() {
            return App.context.getString(R.string.window_phone);
        }
    }
    public class iOS implements SystemAbsProduct{

        @Override
        public String getSystem() {
            return App.context.getString(R.string.ios);
        }
    }

    public class Android implements SystemAbsProduct{

        @Override
        public String getSystem() {
            return App.context.getString(R.string.android);
        }
    }
}

Button:

public interface ButtonAbsProduct {

    //按钮响应
    String getButtonName(SystemAbsProduct system);
    
    public class WindowPhoneButton implements ButtonAbsProduct {

        @Override
        public String getButtonName(SystemAbsProduct system) {
            if (system.getSystem().equals(App.context.getString(R.string.window_phone))) {
                return "点击了WindowPhone系统的Button";
            }
            return App.context.getString(R.string.system_error);
        }
    }

    public class IOSButton implements ButtonAbsProduct {

        @Override
        public String getButtonName(SystemAbsProduct system) {
            if (system.getSystem().equals(App.context.getString(R.string.ios))) {
                return "点击了iOS系统的Button";
            }
            return App.context.getString(R.string.system_error);
        }
    }

    public class AndroidButton implements ButtonAbsProduct {

        @Override
        public String getButtonName(SystemAbsProduct system) {
            if (system.getSystem().equals(App.context.getString(R.string.android))) {
                return "点击了Android系统的Button";
            }
            return App.context.getString(R.string.system_error);
        }
    }
}

因为类的数量庞大,所以我选择使用内部类的形式来实现ConcreteProduct角色。
我们可以发现,在Button角色中,我们进行了简单的判断,当操作系统不匹配时,会提示用户,这就类似于运行崩溃的效果。
接下来,我们来创建最后一个角色:ConcreteFactory。
它应该有多个,每一个负责创建一组具体的产品,也就是说有多少组产品,就应该有多少个具体工厂:
WindowPhoneFactory:

public class WindowPhoneFactory implements AbsFactory {
    @Override
    public SystemAbsProduct createSystem() {
        return new SystemAbsProduct.WindowPhone();
    }

    @Override
    public ButtonAbsProduct createButton() {
        return new ButtonAbsProduct.WindowPhoneButton();
    }
}

IOSFactory:

public class IOSFactory implements AbsFactory {
    @Override
    public SystemAbsProduct createSystem() {
        return new SystemAbsProduct.IOS();
    }

    @Override
    public ButtonAbsProduct createButton() {
        return new ButtonAbsProduct.IOSButton();
    }
}

AndroidFactory:

public class AndroidFactory implements AbsFactory {
    @Override
    public SystemAbsProduct createSystem() {
        return new SystemAbsProduct.Android();
    }

    @Override
    public ButtonAbsProduct createButton() {
        return new ButtonAbsProduct.AndroidButton();
    }
}

三个具体工厂分别具体产生三组相互依赖的产品。
至此,简单的抽象工厂模式已经构建完成。
但是最关键的还是使用,接下来我们来写一些简单的测试代码:

//构建WindowPhone具体工厂
AbsFactory iOSFactory = new IOSFactory();
//构建操作系统
SystemAbsProduct iOSSystem = iOSFactory.createSystem();
//构建按钮
ButtonAbsProduct iOSButton = iOSFactory.createButton();
//调用依赖
Toast.makeText(this, iOSButton.getButtonName(iOSSystem), Toast.LENGTH_SHORT).show();

创建iOS系统工厂,接着创建iOS系统和Button,最后调用Button的点击,会发现成功提示出调用成功。
如果我们使用错误的依赖:

AbsFactory androidFactory1 = new AndroidFactory();
SystemAbsProduct androidSystem1 = androidFactory1.createSystem();
ButtonAbsProduct androidButton1 = androidFactory1.createButton();
AbsFactory iOSFactory1 = new IOSFactory();
SystemAbsProduct iOSSystem1 = iOSFactory1.createSystem();
ButtonAbsProduct iOSButton1 = iOSFactory1.createButton();
//使用Android的Button时,传入iOS操作系统
Toast.makeText(this, androidButton1.getButtonName(iOSSystem1), Toast.LENGTH_SHORT).show();

上述演示代码,会提示系统不匹配。

总结

抽象工厂模式的最大优势就是分离了接口与实现,客户端使用工厂来创建所需对象,实现面向产品的接口编程,使其在切换产品类时更加灵活、容易。
当然缺点也非常明显,一是类文件的增加,二是不太容易扩展产品类,因为每添加一个新产品,都要修改工厂,随之所有工厂实现都要修改。
抽象工厂模式工厂方法模式的区别也很明显:
抽象工厂模式用来生产一组相互依赖的产品。
工厂方法模式用来批量生产一种产品。
抽象工厂模式的相关代码已经上传至GitHub,需要的同学可以下载参考。

感谢

抽象工厂模式和工厂模式的区别-caoglish的回答

《Android源码设计模式解析与实战》 何红辉、关爱民 著

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

推荐阅读更多精彩内容