Provide an interface for creating families of related or dependent objects without specifying their concrete classes.
本篇博客属于《Java设计模式》系列之一,内容主要借鉴于 秦小波的著作《设计模式之禅》,在理解过程中可能还参考其他博主的知识,最后整理成自己的学习笔记,在此分享给大家。由于本人知识和能力有限,博客中有错误或者理解偏差的地方,还望同行及前辈们多多探讨和指点,感谢不尽。(目前本人还在实习阶段,个人认为设计模式还是要结合自己的实际经验去理解更佳,暂时更新最常见设计模式9+1篇。立个flag,将来一定会补齐)–2020.11.22
定义
为创建一组相关或相互依赖的对象提供一个接口,而且无须指定它们的具体类。
相信挺多人第一次看到这个定义都有点懵,这都什么和什么。那么通过一个案例来了解一下。(在学习抽象工厂模式之前,一定要先理解简单工厂模式和工厂模式)
举个栗子
需要一个工厂生产各种主题:白色主题、黑色主题等。
- 白色主题的组成:白色背景、黑色的字体
- 黑色主题的组成:黑色背景、白色的字体
给出类图:
- Factory: 工厂类,定义两个抽象方法。分别是获取背景、获取字体。
- ProductA:产品A抽象类,生产背景。
- ProductB:产品B抽象类,生产字体。
- ConcreteProductA1:产品A实现类,生产黑色背景。
- ConcreteProductA2:产品A实现类,生产白色背景。
- ConcreteProductB1:产品B实现类,生产白色字体。
- ConcreteProductB2:产品B实现类,生产黑色字体。
- ConcreteFactory1:工厂实现类,获取产品A1和产品B1。
- ConcreteFactory2:工厂实现类,获取产品A2和产品B2
如此在业务代码中,实现工厂类factory1,就可以生产黑色背景和白色字体——dark主题,工厂类factory2可以生产白色背景和黑色字体——light主题。
再回到我们的定义看看:为创建一组相关或相互依赖的对象(背景和字体是相关的组件)提供一个接口(工厂类:负责生产主题),而且无须指定它们的具体类(具体类是在工厂实现类里返回具体的背景和字体,对不同工厂的实现类搭配不同的背景和字体,由此生产出各种主题)。
同样的例子还有:一个UI在三个不同平台上(Windows、Linux、macOS)上运行,会怎么设计?分别设计三套不同的应用?非也,通过抽象工厂模式屏蔽掉调操作系统对应用的影响。举其中的 弹窗Bottom和确定OK按钮为例:每种平台的弹窗和按钮都是搭配一组的,不通用。
明确需求:
- 产品抽象类ProductA为弹窗,具体实现类有A1,A2,A3,分别对应windows、Linux、macOS
- 产品抽象类ProductB为按钮,具体实现类有B1,B2,B3,同上。
- 工厂类生产UI,两个抽象方法分别为返回抽象类ProductA,返回抽象类ProductB
- 工厂类有三个实现类,继承并实现了工厂类的两个方法,工厂实现类factory1返回了A1和B1,产生了适合windows平台的UI,同理factory2、factory3.
抽象工厂模式的优点
- 封装性。每个产品的实现类不是高层关心的,高层关心的是接口。我们吃面包不关心面包是怎么种植,怎么运输,怎么制作。我们只知道拿着钱去超市,就能吃到面包。只需要知道工厂类,就能创造一个需要的对象
- 产品族内的约束为非公开。一个面包,在生产过程中黄油和面包配比为2:8。对调用工厂类的高层模块来说是透明的,不知道这个约束,他只需要一个面包早上充饥就行了。
- 安全性。这样不会有因为程序员昨晚加班熬夜,早上生成dark主题的时候,使用了黑色的背景,黑色的字体或者白色背景,黑色的字。
抽象工厂模式的缺点
抽象工厂模式的缺点就是产品族的扩展非常困难。如果要增加一个产品C,也就是说产品家族由原来的2个增加到3个。那么抽象工厂Factory增加一个方法getProductC(),然后两个实现类都要修改。这样违反了开闭原则,抽象类和接口是一个契约。改变契约,所有与契约有关系的代码都要修改。那这就是“有毒代码”…
抽象工厂的产品族扩展困难,但是升级很容易。什么意思呢?拿上面的主题举例,如果要每种主题添加背景音乐,工厂模式新增方法 Music getMusic(),其对应的实现方法均要修改。如果是添加一种非主流主题——红色的字体,绿色的背景。那么在ProductA的实现类新增ConcreteProductA3实现类,生成绿色背景;ProductB的实现类新增ConcreteProductB3实现类,生成红色字体;工厂实现类新增ConcreteFactory3,返回绿色背景红色字体。这样就升级了一款非主流主题。
简单工厂、工厂模式、抽象工厂小结
简单工厂
女娲造人,要一个个捏,要捏鼻子,耳朵,四肢等,还要烤到不同的肤色:白色、黑色、黄色,一步步造好一个人。但是女娲捏着发现太麻烦了,磨刀不误砍柴工,女娲造了一个阴阳炉,在炉子里刻好模板。然后用法力提供不同强度的火焰:火焰大的时候黑人,火焰小的时候白人,火焰刚好的时候黄种人就出来了。
用一个炉子可以造不同肤色的人,这就是简单工厂。具有封装的优点,不需要操心怎么实现,交给阴阳炉就好,具体实现在阴阳炉里。适合简单的场景,一个静态工厂(简单工厂也称作静态工厂,使用静态方法)直接生成需要对象
工厂方法模式
当女娲娘娘觉得世界上就只有三种人有点枯燥,于是决定增加种类,于是有中国人、韩国人、日本人、泰国人、美国人等几十种人,甚至更多。使用简单工厂,一个静态类里定义几十种甚至更多的方法不符合单一职责(应该有且只有一个原因引起类的变更)、不满足依赖倒置原则(面向接口编程:降低类之间的耦合性)。于是可以设计一个阴阳炉图纸(抽象类,目的造人),根据图纸完成各种具体炉子,每个炉子只生产一个国家的人。
定义一个用于创建对象的接口,让子类决定具体实例化哪一个类。工厂方法使一个类的实例化延迟到子类。
抽象工厂模式
简单理解抽象工厂模式是工厂模式的进一步封装。工厂模式只有一个抽象产品类,抽象工厂有着多个关联或依赖的抽象产品类。
具体点的例子:
生产一张床,工厂模式可以实现,可以创造豪华车和普通床。同样工厂模式实现生产桌子、柜子等。生产一栋房子呢,房子里有床、桌子、柜子。土豪房子里豪华床、镀金桌子、保险柜,中产房子里有普通床、四角桌、多功能柜,我家里睡木板床、破桌子、漏风柜子。
这时候抽象工厂定义三个抽象方法分别返回床、桌子、柜子。具体实现类里,土豪房工厂继承三个方法分别返回豪华床x3、镀金桌子x5、保险柜x1,中产房工厂可以返回普通床x2,四角桌x1,多功能柜x1等等…这样也防止你一不小心给我家create了一个豪华床,和我清廉的家风不搭。
工厂模式是一维坐标,要什么直接生产什么。抽象工厂模式是多维坐标(几维取决你的抽象工厂中包含几种抽象方法),房子是三维的:一个房子必须包含床、桌子、柜子。具体什么样子的床、桌子、柜子,可以在工厂实现类里指定和搭配比例。