熟练掌握设计模式是每个程序员的基本功,而在日常工作中运用好设计模式更能提高同事们的工作效率。良好的设计模式不仅可以降低学习成本,还能提升代码的可读性和可维护性。本专栏致力于以通俗易懂、逻辑清晰的方式讲解23种设计模式,帮助读者快速掌握各种设计模式并将其应用到实际项目中,提高编程水平。
目前已完成三篇:
本次带来的是抽象工厂模式。
定义
抽象工厂的定义是:为创建一组相关或相互依赖的对象提供一个接口,并且无须指定它们的具体类。
简单点来说,创建一个抽象工厂类,交于具体的工厂类去实现,并且一个工厂类A被另外一个工厂类B替换时,不会对原有逻辑造成干扰。
示例
目前市面上多数大型App都适配了暗黑模式,同一个页面有两种不同的展现形态,有些样式相同有些不同,如何在完成样式要求的同时保证代码结构清晰、可扩展成了我们设计的目标。
上图是bilibili App在白天模式和黑夜模式的不同样式,他们的主要差别在于背景色和右上角的图标(白天模式是月亮,黑夜模式是太阳),其它的地方都属于相同的样式,这时应该如何设计呢?
工厂方法模式
传送门:白话设计模式之工厂方法模式
第一想法是用工厂方法模式,尝试写下伪代码
有图标工厂生产月亮和太阳图标,颜色工厂生产白色和黑色
if(mode == "白天模式") {
icon = 图标工厂create月亮图标
color = 颜色工厂create白色
} else {
icon = 图标工厂create太阳图标
color = 颜色工厂create黑色
}
// 其它共同的逻辑
headView = 头像工厂create头像
......
图标工厂集中负责图标的创建,颜色工厂集中负责图标的创建,调用方无须关心创建细节。
使用工厂方法模式时问题也很明显:
- 不易扩展,新增一种模式时,需要调整if else逻辑,违反开闭原则。
- 当局部相同、局部不同时,判断逻辑更多,代码更混乱。
抽象工厂模式
创建一个抽象工厂(有创建图标和创建颜色两个抽象方法)分别交由白天模式工厂和黑夜模式工厂去实现;公共的逻辑(比如创建头像等)可以放到抽象工厂里,子工厂可以根据需要去重写。这样就可以通过继承把上面伪代码中的if else消去了。
使用抽象工厂模式可以让代码结构更清晰,并且更容易扩展。
捋一下代码逻辑:
- 需要创建白色实体类和黑色实体类,颜色都有共同的特性,需要再创建一个颜色抽象基类
- 需要创建太阳图标实体类和月亮图标实体类,图标都有共同的特性,需要再创建一个图标抽象基类
- 白天模式工厂中可创建白色和月亮图标
- 黑夜模式工厂中可创建黑色和太阳图标
- 白色模式工厂和黑夜模式工厂共同继承自抽象工厂
代码UML类图如下:
代码的实现如下:
public abstract class AbstractColor {
/**
* 获取子类颜色
*/
public abstract String getColorName();
/**
* 展示颜色
*/
public void showColor() {
System.out.println("当前页面底色为" + getColorName());
}
}
// 黑色
public class BlackColor extends AbstractColor {
@Override
public String getColorName() {
return "黑色";
}
}
// 白色
public class WhiteColor extends AbstractColor {
@Override
public String getColorName() {
return "白色";
}
}
public abstract class AbstractIcon {
/**
* 获取子类的图标名
*/
public abstract String getIconName();
/**
* 展示图标
*/
public void showIcon() {
System.out.println("展示" + getIconName() + "图标");
}
}
// 太阳图标
public class SunIcon extends AbstractIcon {
@Override
public String getIconName() {
// emoji表情
return "☀️";
}
}
// 月亮图标
public class MoonIcon extends AbstractIcon {
@Override
public String getIconName() {
// emoji表情
return "🌛";
}
}
/**
* 抽象工厂
*/
public abstract class AbstractModeFactory {
/**
* 创建图标
*/
public abstract AbstractIcon createIcon();
/**
* 创建背景色
*/
public abstract AbstractColor createColor();
// todo 公共属性可以放到这里,子工厂可重写
}
// 黑夜模式工厂创建太阳图标和黑色背景色
public class DarkModeFactory extends AbstractModeFactory {
@Override
public AbstractIcon createIcon() {
return new SunIcon();
}
@Override
public AbstractColor createColor() {
return new BlackColor();
}
}
// 白天模式工厂创建月亮图标和白色背景色
public class LightModeFactory extends AbstractModeFactory {
@Override
public AbstractIcon createIcon() {
return new MoonIcon();
}
@Override
public AbstractColor createColor() {
return new WhiteColor();
}
}
public class Client {
public static void main(String[] args) {
// 白天模式工厂,当需要变为黑夜模式时,只需要new DarkModeFactory()
AbstractModeFactory factory = new LightModeFactory();
factory.createColor().showColor();
factory.createIcon().showIcon();
}
}
运行结果:
适用场景
当业务有不同的分类,每种业务需要有不同的资源时,使用抽象工厂模式是一种很好的方式。它最大的特点是要求具体工厂类必须语义相同的实现抽象工厂类,以保证工厂类A被工厂类B替换时,下游无感知。比如在黑夜模式客户端调用时只需要改动factory = new DarkModeFactory(),其它逻辑不受任何影响。
注:语义相同指的是方法的目的相同,如createColor()方法,A工厂用它创建背景色,B工厂用它创建字体颜色,这就属于语义不同。
优势
- 扩展性好,增加新的页面样式类型时,只需要让它继承抽象工厂类,别的逻辑无需改动,不受干扰。
- 封装性好,同工厂方法模式一样,调用方无需关心产品实体的创建细节,由具体的工厂统一负责。
劣势
- 当要增加一种新的产品时,比如要求不同样式下展示不同的头像,需要为每个工厂都新增这样的一个方法,我们把这种因新增功能破坏原有封装的行为称作“违反开闭原则”。
工厂方法模式和抽象工厂模式下工厂的区别。
- 工厂方法中的工厂负责创建一类产品,所有这类产品都由它负责,工厂之间没有太大关系。
- 抽象工厂模式中的工厂负责创建多类产品,每类产品只生产一种,所有工厂的方法和属性语义上要求高度一致。
有不合理的地方,还请大佬们不吝赐教。有疑惑的地方,欢迎大家一起来讨论。 文章中的代码放到了Github中。
参考资料:
- 《设计模式之禅》第2版 —— 秦小波著
- 大淘宝技术官方公众号设计模式系列