设计模式完全指南
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):唯一创建
单例类只存在对外共享的唯一实例
java.lang.Runtime,饿汉式java.lang.Math$RandomNumberGeneratorHolder,静态内部类sun.misc.Launcher$ExtClassLoader,双检锁- Spring框架的
ApplicationContext - Java Web的
ServletContext - 线程池
- 数据库连接池
- Config
- 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):封装创建
封装构建细节, 分离构建和使用, 使业务专注于使用
| 模式结构 | 描述 |
|---|---|
| 抽象工厂 | 定义创建抽象产品的接口 |
| 抽象产品 | 定义此类产品的规范、特性、功能 |
| 具体工厂 | 实现抽象工厂,创建具体产品 |
| 具体产品 | 实现抽象产品 |
工厂模式分类:
- 简单工厂模式:无抽象工厂定义,只有具体工厂(如
LocalDateTime、Executors、ThreadLocalRandom等) - 工厂方法模式:抽象工厂只定义一个获取一种抽象产品的接口(如Apache Commons Pool的对象池)
- 抽象工厂模式:抽象工厂定义了多个获取多类相关抽象产品的接口
建造者(Builder):灵活构建
实例拆分为可自由组合的多个基本部分,对复杂实例的构建做了拆分(通过链式调用、可用参数列表等方式)
StringBuilder,构建为StringStream.Builder,构建为Stream<T>- Golang的Option模式
原型(Prototype):拷贝创建
拷贝原型实例的字段到新实例以完成创建,用于创建相同/相似实例的场景,原型类需要实现具体的拷贝行为(即clone方法)
- 浅拷贝:值复制
- 深拷贝:递归值复制
结构型:隐藏组合细节
代理(Proxy):行为委托
代理类与目标类存在相同行为,为目标类封装代理行为细节(事前&事后),而不改变或增强具体实现
- JDK/CGLIB动态代理
- Spring AOP(常用于记录操作日志)
- RPC框架生成的Stub
| 代理类型 | 定义 |
|---|---|
| 静态代理 | 代理类在编译时生成,编译时就能知道代理的是哪个类的实例 |
| 动态代理 | 代理类在运行时生成,运行时才能知道需要代理哪些类的实例 |
装饰器(Decorator):功能增强
装饰器与目标类存在相同行为,改进或增强目标类的实现,通常不修改目标类的功能:
- 缓冲流(
BufferedInputStream、BufferedOutputStream、BufferedReader、BufferedWriter) Collections.synchronized...
适配器(Adapter):适配转换
适配器与目标类存在行为差异,适配器需要作适配逻辑(差异输入/行为适配转换为统一输出/行为)
- 转换流(
InputStreamReader、OutputStreamWriter):将字节流转换为字符流 Executors$RunnableAdapter:Executors.callable将Runnable转换为Callable- SpringMVC框架的
HandlerAdapter:使DispatcherServlet能够以统一的方式调用不同的Handler实现(如基于注解的Controller、基于HttpRequestHandler的Controller、原生Servlet等)
享元(Flyweight):共享复用
基于缓存思想共享复用实例,减少需要创建的实例数量,减少内存占用,提高性能
- 线程池
- 连接池
- JVM常量池
- 全局变量
- 全局配置
- …各种意义上的共享资源池
桥接(Bridge):虚实分离-组合
抽象与实现解耦,基于组合(区别于模板方法)引用抽象
- SPI(Service Provider Interface)机制:JDK定义了基于约定的类加载规则,针对某个用户依赖接口,第三方库可进行不同的实现,用户能够通过JDK直接获取接口实现而无需关注如何创建
- JDBC,加载数据库驱动
- SLF4J,加载不同日志实现
- Spring,各种组件的插拔
- Dubbo,各种组件的插拔
- SpringBoot,启动bean自动装配机制
- Sharding-JDBC,主键生成策略
- Spring框架的IOC
- 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):统一访问
外观聚合内部子系统并对外暴露简化接口,外部统一通过外观接口访问内部子系统(区别于中介者模式,外观模式强调内部与外部的交互)
- SpringMVC框架的
DispatcherServlet - 项目分层中的Controller层
| 模式结构 | 定义 |
|---|---|
| 外观(Facade) | 对外暴露,通常只有一个,封装和协调多个子系统之间的交互 |
| 子系统(Subsystem) | 通常有多个,负责具体的业务逻辑与功能,处理外观指派的任务 |
组合(Composite):树形处理
树形结构处理,叶子节点与树枝节点一视同仁
- GUI组件
- 文件系统
- 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):虚实分离-继承
抽象与实现解耦,基于继承(区别于桥接模式)引用抽象,父类定义框架,子类实现抽象
- 线程池的
AbstractExecutorService - 集合框架定义的
AbstractMap、AbstractCollection、AbstractList、AbstractSet等模板类 - 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):链式责任
处理链路上的每个节点具有不同的责任
- 审批流
- 异常处理(try-catch-throw)
- RPC Middleware
中介者(Mediator):中介解耦
中介者(中间层)封装所有参与者之间的交互逻辑,所有参与者关注与中介者的交互即可,网状通信改为星型通信(区别于外观模式,中介者模式强调内部交互)
- GUI控件协调:比如一个表单的按钮、文本框、下拉框等多个控件存在复杂联动,需要设计一个表单控制器(中介者)进行协调,避免控件间的直接依赖
- 聊天系统:群聊场景中,用户不直接发送消息到其他用户,而是通过聊天服务器(中介者)转发
- 电商系统:商家和用户是参与者,平台是中介者
- 数据库多对多关系表
访问者(Visitor):操作解耦
感觉是设计模式中较难理解的一个,可以看下以下资料:
- https://zhuanlan.zhihu.com/p/380161731
- https://juejin.cn/post/7228764712132575287
- https://blog.csdn.net/qq_73574147/article/details/136460582
- https://blog.csdn.net/csdn_tom_168/article/details/148412889
访问者将数据结构与操作解耦,能够在不修改数据结构的情况下添加新的操作,适用于数据结构稳定但需要频繁扩展操作的场景
动态双分派(两次连续的动态方法调用):
- 首次分派:调用
accept(Visitor)方法,会根据Element的实际类型确定accept的实际调用方法- 再次分派:在
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):命令封装
命令封装为类
- 数据库事务命令,如begin、commit、rollback等
- 操作系统命令,如cd、ls等
备忘录(Memento):快照恢复
将当前数据状态保存为快照,以支持后续的回滚和恢复操作
- 撤销回滚
- 备份恢复
解释器(Interpreter):文法解析
定义语言的文法规则,能够将字符串输入解析为语法树,再基于语法树进行操作
- 编程语言语法
- SQL语法
- JSON语法
- 正则表达式
- SpEL表达式
其他设计模式
不可变对象(Immutable Object)
对象创建后便处于只读状态,状态及属性在生命周期内不变,基于写时复制(CopyOnWrite)保证并发安全
- Java的
String、包装类(如Integer、Long等)、 新日期API(如LocalDateTime、LocalDate、LocalTime等) - Golang的
time.Time
空对象(Null Object)
空对象具有特定接口或抽象类的默认行为(写时无操作,读时返回安全默认值),消除繁琐空值检查,避免潜在空指针异常,使代码更简洁、更健壮
Collections.empty...- Apache Commons IO的
NullInputStream和NullOutputStream - Golang的
io.Discard
结语
设计模式有四境界:
- 没学前是一点不懂,根本想不到用设计模式,设计的代码很糟糕;
- 学了几个模式后,很开心,于是到处想着要用自己学过的模式,于是时常造成误用模式而不自知;
- 学完全部模式时,感觉诸多模式极其相似,无法分清模式之间的差异,有困惑,但深知误用之害,应用之时有所犹豫;
- 灵活应用模式,甚至不应用具体的某种模式也能设计出非常优秀的代码,以达到无剑胜有剑的境界。
- 不能死记硬背设计模式,实践中往往需要基于某个设计模式思想或结合多个设计模式去设计
- 不能为了用而用,遵守KISS原则和YAGNI原则,聚焦当下实现简单直接的设计,可不使用设计模式,以避免复杂化