文章

设计模式完全指南

10 大设计原则与 23 种经典设计模式(创建型、结构型、行为型)

设计模式完全指南

设计原则

设计原则理论描述理解
开闭原则软件实体(模块、类、函数等)对扩展开放,对修改关闭添加新功能无需修改现有代码
单一职责原则一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分类的设计原则:职责单一,避免臃肿
接口隔离原则1、客户端不应该被迫依赖它不使用的方法
2、一个类对另一个类的依赖应当基于最小的接口
1、接口设计原则:接口应尽量拆分细化
2、接口使用原则:应依赖功能最小的接口
依赖倒置原则1、高层模块不应依赖低层模块,两者都应该依赖其抽象
2、抽象不应依赖于细节,细节应依赖于抽象
多态:面向接口编程,依赖接口而不依赖具体实现
里氏替换原则继承应确保超类所拥有的性质在子类中仍成立继承:子类应扩展而不修改
迪米特法则只与你最亲近的朋友交谈,不要与陌生人交流封装:只依赖该依赖的实体,只暴露该暴露的模块
合成复用原则尽量使用组合(Composition)或聚合(Aggregation),
而不用继承(Inheritance)来达到复用的目的
基于继承机制复用代码时,优先考虑修改为组合/聚合
KISS原则Keep It Simple, Stupid保持简单直接的设计和实现(但也尽量关注性能)
DRY原则Don’t Repeat Yourself封装重复逻辑,降低维护成本
YAGNI原则You Aren’t Gonna Need It聚焦当前需求,避免过度设计(但也尽量保证可扩展性)

设计模式

创建型:隐藏创建细节

单例(Singleton):唯一创建

单例类只存在对外共享的唯一实例

  1. java.lang.Runtime,饿汉式
  2. java.lang.Math$RandomNumberGeneratorHolder,静态内部类
  3. sun.misc.Launcher$ExtClassLoader,双检锁
  4. Spring框架的ApplicationContext
  5. Java Web的ServletContext
  6. 线程池
  7. 数据库连接池
  8. Config
  9. Logger

1、饿汉式:启动时实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Singleton {

  // 方式1:静态声明时初始化
  private static Singleton singleton = new Singleton();

  // 方式2:静态初始化块
  static {
    // ...
    singleton = new Singleton();
    // ...
  }

  // 私有构造器
  private Singleton() {
  }

  public static Singleton instance() {
    return singleton;
  }
}

2、懒汉式:使用时实例化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Singleton {
  private static Singleton singleton;

  // 私有构造器
  private Singleton() {
  }

  // synchronized修饰以避免并发初始化
  public static synchronized Singleton instance() {
    if (singleton == null) {
      singleton = new Singleton();
    }
    return singleton;
  }
}

3、双检锁:懒汉式变种,仅单例初始化时加锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Singleton {

  // volatile修饰以避免其他线程获取未初始化完全的单例实例
  private static volatile Singleton singleton;

  // 私有构造器
  private Singleton() {
  }

  public static Singleton instance() {
    if (singleton == null) {
      synchronized (Singleton.class) {
        if (singleton == null) {
          // singleton = new Singleton() 可解释为
          //  1. ref_addr = malloc(), 申请内存空间
          //  2. init(ref_addr), 实例初始化, 即执行构造器逻辑
          //  3. singleton = ref_addr, 引用变量赋值
          // 
          // 其中2和3因不存在数据依赖关系可重排序, volatile可禁止3发生重排序
          singleton = new Singleton();
        }
      }
    }
    return singleton;
  }
}

4、静态内部类:懒汉式变种,基于静态内部类延迟加载和<clinit>()加锁的特性实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package java.lang;

// JDK内置工具类Math
public final class Math {
  // ...
  private static final class RandomNumberGeneratorHolder {
    static final Random randomNumberGenerator = new Random();
  }

  public static double random() {
    return RandomNumberGeneratorHolder.randomNumberGenerator.nextDouble();
  }
  // ...
}

5、枚举:饿汉式变种,基于enum关键字实现

工厂(Factory):封装创建

封装构建细节, 分离构建和使用, 使业务专注于使用

模式结构描述
抽象工厂定义创建抽象产品的接口
抽象产品定义此类产品的规范、特性、功能
具体工厂实现抽象工厂,创建具体产品
具体产品实现抽象产品

工厂模式分类:

  1. 简单工厂模式:无抽象工厂定义,只有具体工厂(如LocalDateTimeExecutorsThreadLocalRandom等)
  2. 工厂方法模式:抽象工厂只定义一个获取一种抽象产品的接口(如Apache Commons Pool的对象池)
  3. 抽象工厂模式:抽象工厂定义了多个获取多类相关抽象产品的接口

建造者(Builder):灵活构建

实例拆分为可自由组合的多个基本部分,对复杂实例的构建做了拆分(通过链式调用、可用参数列表等方式)

  1. StringBuilder,构建为String
  2. Stream.Builder,构建为Stream<T>
  3. Golang的Option模式

原型(Prototype):拷贝创建

拷贝原型实例的字段到新实例以完成创建,用于创建相同/相似实例的场景,原型类需要实现具体的拷贝行为(即clone方法)

  1. 浅拷贝:值复制
  2. 深拷贝:递归值复制

结构型:隐藏组合细节

代理(Proxy):行为委托

代理类与目标类存在相同行为,为目标类封装代理行为细节(事前&事后),而不改变或增强具体实现

  1. JDK/CGLIB动态代理
  2. Spring AOP(常用于记录操作日志)
  3. RPC框架生成的Stub
代理类型定义
静态代理代理类在编译时生成,编译时就能知道代理的是哪个类的实例
动态代理代理类在运行时生成,运行时才能知道需要代理哪些类的实例

装饰器(Decorator):功能增强

装饰器与目标类存在相同行为,改进或增强目标类的实现,通常不修改目标类的功能:

  1. 缓冲流(BufferedInputStreamBufferedOutputStreamBufferedReaderBufferedWriter
  2. Collections.synchronized...

适配器(Adapter):适配转换

适配器与目标类存在行为差异,适配器需要作适配逻辑(差异输入/行为适配转换为统一输出/行为)

  1. 转换流(InputStreamReaderOutputStreamWriter):将字节流转换为字符流
  2. Executors$RunnableAdapterExecutors.callableRunnable转换为Callable
  3. SpringMVC框架的HandlerAdapter:使DispatcherServlet 能够以统一的方式调用不同的Handler实现(如基于注解的Controller、基于HttpRequestHandler的Controller、原生Servlet等)

享元(Flyweight):共享复用

基于缓存思想共享复用实例,减少需要创建的实例数量,减少内存占用,提高性能

  1. 线程池
  2. 连接池
  3. JVM常量池
  4. 全局变量
  5. 全局配置
  6. …各种意义上的共享资源池

桥接(Bridge):虚实分离-组合

抽象与实现解耦,基于组合(区别于模板方法)引用抽象

  1. SPI(Service Provider Interface)机制:JDK定义了基于约定的类加载规则,针对某个用户依赖接口,第三方库可进行不同的实现,用户能够通过JDK直接获取接口实现而无需关注如何创建
    • JDBC,加载数据库驱动
    • SLF4J,加载不同日志实现
    • Spring,各种组件的插拔
    • Dubbo,各种组件的插拔
    • SpringBoot,启动bean自动装配机制
    • Sharding-JDBC,主键生成策略
  2. Spring框架的IOC
  3. Logger(如下代码所示)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
interface Logger {
  void info(String msg);
  void warn(String msg);
  void error(String msg);
}

class Service {

  Logger logger; // logger可以是控制台、文件

  public void business() {
    logger.info("开始");
    try {
      // ... 业务逻辑
      logger.info("结束");
    } catch (Exception e) {
      // ... 异常处理
      logger.error("异常");
      throw e;
    }
  }
}

外观(Facade):统一访问

外观聚合内部子系统并对外暴露简化接口,外部统一通过外观接口访问内部子系统(区别于中介者模式,外观模式强调内部与外部的交互)

  1. SpringMVC框架的DispatcherServlet
  2. 项目分层中的Controller层
模式结构定义
外观(Facade)对外暴露,通常只有一个,封装和协调多个子系统之间的交互
子系统(Subsystem)通常有多个,负责具体的业务逻辑与功能,处理外观指派的任务

组合(Composite):树形处理

树形结构处理,叶子节点与树枝节点一视同仁

  1. GUI组件
  2. 文件系统
  3. Golang的context
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
// 文件系统组件定义
interface FileSystemComponent {
  long getSize();
}

// 文件
class File implements FileSystemComponent {
  String name;
  long size;

  @Override
  public long getSize() {
    return size;
  }
}

// 目录
class Folder implements FileSystemComponent {
  List<FileSystemComponent> childs = new ArrayList<>();

  void addComponent(FileSystemComponent component) {
    childs.add(component);
  }

  @Override
  public long getSize() {
    long size = 0;
    for (FileSystemComponent component : childs) {
      size += component.getSize();
    }
    return size;
  }
}


行为型:隐藏交互细节

模板方法(Template Method):虚实分离-继承

抽象与实现解耦,基于继承(区别于桥接模式)引用抽象,父类定义框架,子类实现抽象

  1. 线程池的AbstractExecutorService
  2. 集合框架定义的AbstractMapAbstractCollectionAbstractListAbstractSet等模板类
  3. Java Web的HttpServlet

策略(Strategy):外部驱动

环境(外部参数)决定策略(区别于状态模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// 运算符策略定义
interface Operator {
  int apply(int a, int b);
}

// 运算符策略-加法
class OperatorAdd implements Operator {
  @Override
  public int apply(int a, int b) {
    return a + b;
  }
}

// 运算符策略-减法
class OperatorSub implements Operator {
  @Override
  public int apply(int a, int b) {
    return a - b;
  }
}

// 表达式定义
class Expression {
  Operator op;
  int a;
  int b;

  Expression(Operator op, int a, int b) {
    this.op = op;
    this.a = a;
    this.b = b;
  }

  int calculate() {
    return op.apply(a, b);
  }
}

// 策略模式示例
class Main {
  public static void main(String[] args) {
    // 传入add策略
    Expression expr1 = new Expression(new OpearatorAdd(), 10, 5);
    System.out.println(expr1.calculate());
    // 传入sub策略
    Expression expr2 = new Expression(new OperatorSub(), 10, 5);
    System.out.println(expr2.calculate());
  }
}

状态(State):内部驱动

状态(内部字段)决定行为

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 审批状态机定义
interface ApprovalStateMachine {
  int approve(); // 审批通过,返回下个状态值
  int reject(); // 审批驳回,返回下个状态值
}

// owner审批(一级审批)
class OwnerApproval implements ApprovalStateMachine {
  // ...
}

// leader审批(二级审批)
class LeaderApproval implements ApprovalStateMachine {
  // ...
}

// 审批任务定义
class AuditTask {
  int id; // 主键id
  int state; // 审批状态

  AuditTask() {
    this.state = 1; // 默认从一级审批开始 
  }

  // 状态(内部)决定行为
  private ApprovalStateMachine getApprovalStateMachine() {
    // 这里也可以使用map处理,避免写switch
    switch (this.state) {
      case 1:
        return new OwnerApproval();
      case 2:
        return new LeaderApproval();
      case 99:
        throw new IllegalArgumentException("审批已通过");
      case 10000:
        throw new IllegalArgumentException("审批已驳回");
      default:
        throw new IllegalArgumentException("审批状态异常");
    }
  }

  // 审批通过
  public void approve() {
    ApprovalStateMachine stateMachine = getApprovalStateMachine();
    this.state = stateMachine.approve();
  }

  // 审批驳回
  public void reject() {
    ApprovalStateMachine stateMachine = getApprovalStateMachine();
    this.state = stateMachine.reject();
  }
}

// 状态模式示例
class Main {
  public static void main(String[] args) {
    AuditTask task = new AuditTask();
    task.approve(); // 一级审批通过
    task.approve(); // 二级审批通过
    task.approve(); // 审批已通过,抛出异常
  }
}

迭代器(Iterator):迭代解耦

定义统一迭代逻辑,隐藏聚合结构底层细节(隐藏比如数组通过索引+1、链表通过next指针实现遍历等细节)

1、迭代器定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public interface Iterator<E> {
  // 检查游标是否指向有效元素
  boolean hasNext();

  // 返回游标指向的当前元素并更新游标
  E next();

  // 从聚合结构中删除当前next指向的对象
  default void remove() {
    throw new UnsupportedOperationException("remove");
  }

  // 统一迭代逻辑
  default void forEachRemaining(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    while (hasNext())
      action.accept(next());
  }
}

2、可迭代接口定义,聚合结构实现此接口后可使用foreach语法糖

1
2
3
public interface Iterable<E> {
  Iterator<E> getIterator();
}

观察者(Observer):发布订阅

事件发布订阅模型,JDK内置观察者模式如下

1、观察者定义

1
2
3
4
public interface Observer {
  // 事件回调
  void update(Observable o, Object arg);
}

2、可观察类定义(简化)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class Observable {
  private Vector<Observer> obs;

  // 添加观察者(添加订阅)
  public synchronized void addObserver(Observer o) {
    if (o == null)
      throw new NullPointerException();
    if (!obs.contains(o)) {
      obs.addElement(o);
    }
  }

  // 删除观察者(删除订阅)
  public synchronized void deleteObserver(Observer o) {
    obs.removeElement(o);
  }

  // 事件发布
  public void notifyObservers(Object arg) {
    Object[] arrLocal;
    synchronized (this) {
      arrLocal = obs.toArray();
    }
    for (int i = arrLocal.length - 1; i >= 0; i--)
      ((Observer) arrLocal[i]).update(this, arg);
  }
}

责任链(Chain of Responsibility):链式责任

处理链路上的每个节点具有不同的责任

  1. 审批流
  2. 异常处理(try-catch-throw)
  3. RPC Middleware

中介者(Mediator):中介解耦

中介者(中间层)封装所有参与者之间的交互逻辑,所有参与者关注与中介者的交互即可,网状通信改为星型通信(区别于外观模式,中介者模式强调内部交互)

  1. GUI控件协调:比如一个表单的按钮、文本框、下拉框等多个控件存在复杂联动,需要设计一个表单控制器(中介者)进行协调,避免控件间的直接依赖
  2. 聊天系统:群聊场景中,用户不直接发送消息到其他用户,而是通过聊天服务器(中介者)转发
  3. 电商系统:商家和用户是参与者,平台是中介者
  4. 数据库多对多关系表

访问者(Visitor):操作解耦

感觉是设计模式中较难理解的一个,可以看下以下资料:

  1. https://zhuanlan.zhihu.com/p/380161731
  2. https://juejin.cn/post/7228764712132575287
  3. https://blog.csdn.net/qq_73574147/article/details/136460582
  4. https://blog.csdn.net/csdn_tom_168/article/details/148412889

访问者将数据结构与操作解耦,能够在不修改数据结构的情况下添加新的操作,适用于数据结构稳定但需要频繁扩展操作的场景

动态双分派(两次连续的动态方法调用):

  1. 首次分派:调用accept(Visitor)方法,会根据Element的实际类型确定accept的实际调用方法
  2. 再次分派:在accept方法内部调用visitor.visit(this),会再根据Visitor的实际类型确定visit的实际调用方法

1、访问者定义(类似升级版策略模式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
interface AnimalVisitor {
  void visit(Panda Panda);
  void visit(Elephant elephant);
  void visit(Giraffe giraffe);
}

class AnimalKeeper implements AnimalVisitor {
  @Override
  public void visit(Panda panda) {
    System.out.println("喂养熊猫" + panda.getName());
  }

  @Override
  public void visit(Elephant elephant) {
    System.out.println("喂养大象" + elephant.getName());
  }

  @Override
  public void visit(Giraffe giraffe) {
    System.out.println("喂养长颈鹿" + giraffe.getName());
  }
}

class AnimalDoctor implements AnimalVistor {
  @Override
  public void visit(Panda panda) {
    System.out.println("医治熊猫" + panda.getName());
  }

  @Override
  public void visit(Elephant elephant) {
    System.out.println("医治大象" + elephant.getName());
  }

  @Override
  public void visit(Giraffe giraffe) {
    System.out.println("医治长颈鹿" + giraffe.getName());
  }
}

2、元素定义

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
abstract class Animal {
  private String name;

  void setName(String name) {
    this.name = name;
  }

  String getName() {
    return this.name;
  }

  abstract void accept(AnimalVisitor visitor);
}

class Panda extends Animal {

  @Override
  public void accept(AnimalVisitor visitor) {
    visitor.visit(this);
  }
}

class Elephant extends Animal {

  @Override
  public void accept(AnimalVisitor visitor) {
    visitor.visit(this);
  }
}

class Giraffe extends Animal {

  @Override
  public void accept(AnimalVisitor visitor) {
    visitor.visit(this);
  }
}

命令(Command):命令封装

命令封装为类

  1. 数据库事务命令,如begin、commit、rollback等
  2. 操作系统命令,如cd、ls等

备忘录(Memento):快照恢复

将当前数据状态保存为快照,以支持后续的回滚和恢复操作

  1. 撤销回滚
  2. 备份恢复

解释器(Interpreter):文法解析

定义语言的文法规则,能够将字符串输入解析为语法树,再基于语法树进行操作

  1. 编程语言语法
  2. SQL语法
  3. JSON语法
  4. 正则表达式
  5. SpEL表达式

其他设计模式

不可变对象(Immutable Object)

对象创建后便处于只读状态,状态及属性在生命周期内不变,基于写时复制(CopyOnWrite)保证并发安全

  1. Java的String、包装类(如IntegerLong等)、 新日期API(如LocalDateTimeLocalDateLocalTime等)
  2. Golang的time.Time

空对象(Null Object)

空对象具有特定接口或抽象类的默认行为(写时无操作,读时返回安全默认值),消除繁琐空值检查,避免潜在空指针异常,使代码更简洁、更健壮

  1. Collections.empty...
  2. Apache Commons IO的NullInputStreamNullOutputStream
  3. Golang的io.Discard

结语

设计模式有四境界:

  1. 没学前是一点不懂,根本想不到用设计模式,设计的代码很糟糕;
  2. 学了几个模式后,很开心,于是到处想着要用自己学过的模式,于是时常造成误用模式而不自知;
  3. 学完全部模式时,感觉诸多模式极其相似,无法分清模式之间的差异,有困惑,但深知误用之害,应用之时有所犹豫;
  4. 灵活应用模式,甚至不应用具体的某种模式也能设计出非常优秀的代码,以达到无剑胜有剑的境界。
  1. 不能死记硬背设计模式,实践中往往需要基于某个设计模式思想或结合多个设计模式去设计
  2. 不能为了用而用,遵守KISS原则和YAGNI原则,聚焦当下实现简单直接的设计,可不使用设计模式,以避免复杂化
本文由作者按照 CC BY 4.0 进行授权