白话设计模式之抽象工厂模式

260 阅读6分钟

熟练掌握设计模式是每个程序员的基本功,而在日常工作中运用好设计模式更能提高同事们的工作效率。良好的设计模式不仅可以降低学习成本,还能提升代码的可读性和可维护性。本专栏致力于以通俗易懂、逻辑清晰的方式讲解23种设计模式,帮助读者快速掌握各种设计模式并将其应用到实际项目中,提高编程水平。

目前已完成三篇:

本次带来的是抽象工厂模式。

定义

抽象工厂的定义是:为创建一组相关或相互依赖的对象提供一个接口,并且无须指定它们的具体类。

简单点来说,创建一个抽象工厂类,交于具体的工厂类去实现,并且一个工厂类A被另外一个工厂类B替换时,不会对原有逻辑造成干扰。

示例

目前市面上多数大型App都适配了暗黑模式,同一个页面有两种不同的展现形态,有些样式相同有些不同,如何在完成样式要求的同时保证代码结构清晰、可扩展成了我们设计的目标。

上图是bilibili App在白天模式和黑夜模式的不同样式,他们的主要差别在于背景色和右上角的图标(白天模式是月亮,黑夜模式是太阳),其它的地方都属于相同的样式,这时应该如何设计呢?

工厂方法模式

传送门:白话设计模式之工厂方法模式

第一想法是用工厂方法模式,尝试写下伪代码

有图标工厂生产月亮和太阳图标,颜色工厂生产白色和黑色
if(mode == "白天模式") {
    icon = 图标工厂create月亮图标
    color = 颜色工厂create白色
} else {
    icon = 图标工厂create太阳图标
    color = 颜色工厂create黑色
}    

// 其它共同的逻辑
headView = 头像工厂create头像
......

图标工厂集中负责图标的创建,颜色工厂集中负责图标的创建,调用方无须关心创建细节。

使用工厂方法模式时问题也很明显:

  • 不易扩展,新增一种模式时,需要调整if else逻辑,违反开闭原则。
  • 当局部相同、局部不同时,判断逻辑更多,代码更混乱。

抽象工厂模式

创建一个抽象工厂(有创建图标和创建颜色两个抽象方法)分别交由白天模式工厂和黑夜模式工厂去实现;公共的逻辑(比如创建头像等)可以放到抽象工厂里,子工厂可以根据需要去重写。这样就可以通过继承把上面伪代码中的if else消去了。

使用抽象工厂模式可以让代码结构更清晰,并且更容易扩展。

捋一下代码逻辑:

  1. 需要创建白色实体类和黑色实体类,颜色都有共同的特性,需要再创建一个颜色抽象基类
  2. 需要创建太阳图标实体类和月亮图标实体类,图标都有共同的特性,需要再创建一个图标抽象基类
  3. 白天模式工厂中可创建白色和月亮图标
  4. 黑夜模式工厂中可创建黑色和太阳图标
  5. 白色模式工厂和黑夜模式工厂共同继承自抽象工厂

代码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版 —— 秦小波著
  • 大淘宝技术官方公众号设计模式系列