设计模式讲的是如何写出可扩展、可读、可维护的⾼质量代码,所以,它们跟平时的编码会有直接的关系,也会直 接影响到你的开发能⼒。

什么是面向对象编程?

⾯向对象编程的英⽂缩写是 OOP,全称是 Object Oriented Programming。对应地,⾯向对象编程语⾔的英⽂缩 写是 OOPL,全称是 Object Oriented Programming Language。 ⾯向对象编程中有两个⾮常重要、⾮常基础的概念,那就是类(class)和对象(object)。这两个概念最早出现在 1960 年,在 Simula 这种编程语⾔中第⼀次使⽤。⽽⾯向对象编程这个概念第⼀次被使⽤是在 Smalltalk 这种编程 语⾔中。Smalltalk 被认为是第⼀个真正意义上的⾯向对象编程语⾔。 如果⾮得给出⼀个定义的话,我觉得可以⽤下⾯两句话来概括 ⾯向对象编程是⼀种编程范式或编程⻛格。

  • 它以类或对象作为组织代码的基本单元,并将封装、抽象、继 承、多态四个特性,作为代码设计和实现的基⽯ 。
  • ⾯向对象编程语⾔是⽀持类或对象的语法机制,并有现成的语法机制,能⽅便地实现⾯向对象编程四⼤特性 (封装、抽象、继承、多态)的编程语⾔。

⾯向对象编程是⼀种编程范式或编程⻛格。它以类或对象作为组织代码的基本单元,并将封装、抽象、继承、多态 四个特性,作为代码设计和实现的基⽯。

⾯向对象编程语⾔是⽀持类或对象的语法机制,并有现成的语法机制,能⽅便地实现⾯向对象编程四⼤特性(封 装、抽象、继承、多态)的编程语⾔。

如果按照严格的的定义,需要有现成的语法⽀持类、对象、四⼤特性才能叫作⾯向对象编程语⾔。如果放宽要求的 话,只要某种编程语⾔⽀持类、对象语法机制,那基本上就可以说这种编程语⾔是⾯向对象编程语⾔了,不⼀定⾮ 得要求具有所有的四⼤特性。

面向对象的四大特性

封装

封装也叫作信息隐藏或者数据访问保护。类通过暴露有限的访问接⼝,授权外部仅能通过类提供的⽅式来访问内部 信息或者数据。它需要编程语⾔提供权限访问控制语法来⽀持,例如 Java 中的 private、protected、public 关键 字。封装特性存在的意义,⼀⽅⾯是保护数据不被随意修改,提⾼代码的可维护性;另⼀⽅⾯是仅暴露有限的必要 接⼝,提⾼类的易⽤性。

抽象

封装主要讲如何隐藏信息、保护数据,那抽象就是讲如何隐藏⽅法的具体实现,让使⽤者只需要关⼼⽅法提供了哪 些功能,不需要知道这些功能是如何实现的。抽象可以通过接⼝类或者抽象类来实现,但也并不需要特殊的语法机 制来⽀持。抽象存在的意义,⼀⽅⾯是提⾼代码的可扩展性、维护性,修改实现不需要改变定义,减少代码的改动 范围;另⼀⽅⾯,它也是处理复杂系统的有效⼿段,能有效地过滤掉不必要关注的信息。

继承

继承是⽤来表示类之间的 is-a 关系,分为两种模式:单继承和多继承。单继承表示⼀个⼦类只继承⼀个⽗类,多继 承表示⼀个⼦类可以继承多个⽗类。为了实现继承这个特性,编程语⾔需要提供特殊的语法机制来⽀持。继承主要 是⽤来解决代码复⽤的问题。

多态

多态是指⼦类可以替换⽗类,在实际的代码运⾏过程中,调⽤⼦类的⽅法实现。多态这种特性也需要编程语⾔提供 特殊的语法机制来实现,⽐如继承、接⼝类、duck-typing。多态可以提⾼代码的扩展性和复⽤性,是很多设计模 式、设计原则、编程技巧的代码实现基础。

抽象类和接口

抽象类

  • 抽象类不允许被实例化,只能被继承。也就是说,你不能 new ⼀个抽象类的对象出来(Logger logger = new Logger(…); 会报编译错误)。
  • 抽象类可以包含属性和⽅法。⽅法既可以包含代码实现(⽐如 Logger 中的 log() ⽅法),也可以不包含代码 实现(⽐如 Logger 中的 doLog() ⽅法)。不包含代码实现的⽅法叫作抽象⽅法。
  • ⼦类继承抽象类,必须实现抽象类中的所有抽象⽅法。对应到例⼦代码中就是,所有继承 Logger 抽象类的⼦ 类,都必须重写 doLog() ⽅法。

接口特性

  • 接⼝不能包含属性(也就是成员变量)。
  • 接⼝只能声明⽅法,⽅法不能包含代码实现。
  • 类实现接⼝的时候,必须实现接⼝中声明的所有⽅法。

抽象类实际上就是类,只不过是⼀种特殊的类,这种类不能被实例化为对象,只能被⼦类继承。我们知道,继承关 系是⼀种 is-a 的关系,那抽象类既然属于类,也表示⼀种 is-a 的关系。相对于抽象类的 is-a 关系来说,接⼝表示 ⼀种 has-a 关系,表示具有某些功能。对于接⼝,有⼀个更加形象的叫法,那就是协议(contract)。

应用场景

抽象类更多的是为了代码复⽤,⽽接⼝就更侧重于解耦。接⼝是对⾏为的⼀种抽象,相当于⼀组协议或者契约,你 可以联想类⽐⼀下 API 接⼝。调⽤者只需要关注抽象的接⼝,不需要了解具体的实现,具体的实现代码对调⽤者透 明。接⼝实现了约定和实现相分离,可以降低代码间的耦合性,提⾼代码的可扩展性。

抽象类是对成员变量和⽅法的抽象,是⼀种 is-a 关系,是为了解决代码复⽤问题。接⼝仅仅是对⽅法的抽象,是⼀ 种 has-a 关系,表示具有某⼀组⾏为特性,是为了解决解耦问题,隔离接⼝和具体的实现,提⾼代码的扩展性。如果要表示⼀种 is-a 的关系,并且是为了 解决代码复⽤问题,我们就⽤抽象类;如果要表示 ⼀种 has-a 关系,并且是为了解决抽象⽽⾮代码复⽤问题,那我 们就⽤接⼝。

基于接口而非实现编程

“基于接⼝⽽⾮实现编程”这条原则的另⼀个表述⽅式,是“基于抽象⽽⾮实现编程”。后者的表述⽅式 其实更能体现这条原则的设计初衷。在软件开发中,最⼤的挑战之⼀就是需求的不断变化,这也是考验代码 设计好坏的⼀个标准。越抽象、越顶层、越脱离具体某⼀实现的设计,越能提⾼代码的灵活性,越能应对未 来的需求变化。好的代码设计,不仅能应对当下的需求,⽽且在将来需求发⽣变化的时候,仍然能够在不破 坏原有代码设计的情况下灵活应对。⽽抽象就是提⾼代码扩展性、灵活性、可维护性最有效的⼿段之⼀。

我们在定义接⼝的时候,⼀⽅⾯,命名要⾜够通⽤,不能包含跟具体实现相关的字眼;另⼀⽅⾯,与特定实 现有关的⽅法不要定义在接⼝中。 “基于接⼝⽽⾮实现编程”这条原则,不仅仅可以指导⾮常细节的编程开发,还能指导更加上层的架构设计、系 统设计等。⽐如,服务端与客户端之间的“接⼝”设计、类库的“接⼝”设计。

为什么不推荐使用继承

继承是⾯向对象的四⼤特性之⼀,⽤来表示类之间的 is-a 关系,可以解决代码复⽤的问题。虽然继承有诸多作⽤, 但继承层次过深、过复杂,也会影响到代码的可维护性。在这种情况下,我们应该尽量少⽤,甚⾄不⽤继承。

继承主要有三个作⽤:表示 is-a 关系,⽀持多态特性,代码复⽤。⽽这三个作⽤都可以通过其他技术⼿段 来达成。⽐如 is-a 关系,我们可以通过组合和接⼝的 has-a 关系来替代;多态特性我们可以利⽤接⼝来实现;代码复⽤我们可以通过组合和委托来实现。所以,从理论上讲,通过组合、接⼝、委托三个技术⼿段,我们完全可以 替换掉继承,在项⽬中不⽤或者少⽤继承关系,特别是⼀些复杂的继承关系。

设计模式的六大原则

1、开闭原则(Open Close Principle)

开闭原则的意思是:对扩展开放,对修改关闭。在程序需要进行拓展的时候,不能去修改原有的代码,实现一个热插拔的效果。简言之,是为了使程序的扩展性好,易于维护和升级。想要达到这样的效果,我们需要使用接口和抽象类,后面的具体设计中我们会提到这点。

2、里氏代换原则(Liskov Substitution Principle)

里氏代换原则是面向对象设计的基本原则之一。 里氏代换原则中说,任何基类可以出现的地方,子类一定可以出现。LSP 是继承复用的基石,只有当派生类可以替换掉基类,且软件单位的功能不受到影响时,基类才能真正被复用,而派生类也能够在基类的基础上增加新的行为。里氏代换原则是对开闭原则的补充。实现开闭原则的关键步骤就是抽象化,而基类与子类的继承关系就是抽象化的具体实现,所以里氏代换原则是对实现抽象化的具体步骤的规范。

3、依赖倒转原则(Dependence Inversion Principle)

这个原则是开闭原则的基础,具体内容:针对接口编程,依赖于抽象而不依赖于具体。

4、接口隔离原则(Interface Segregation Principle)

这个原则的意思是:使用多个隔离的接口,比使用单个接口要好。它还有另外一个意思是:降低类之间的耦合度。由此可见,其实设计模式就是从大型软件架构出发、便于升级和维护的软件设计思想,它强调降低依赖,降低耦合。

5、迪米特法则,又称最少知道原则(Demeter Principle)

最少知道原则是指:一个实体应当尽量少地与其他实体之间发生相互作用,使得系统功能模块相对独立。

6、合成复用原则(Composite Reuse Principle)

合成复用原则是指:尽量使用合成/聚合的方式,而不是使用继承。

工厂模式

在软件开发中,对象创建逻辑的复杂性可能导致代码重复、耦合度高、难以扩展。例如:

  • 直接通过 new 关键字实例化对象时,客户端代码需要依赖具体类名。
  • 当新增或修改对象类型时,需要频繁修改客户端代码,违反开闭原则(对扩展开放,对修改关闭)。

工厂模式通过封装对象创建过程,将客户端代码与具体类解耦,从而解决这一问题。

工厂模式是一种创建型设计模式,核心思想是通过一个公共接口(工厂类或工厂方法)创建对象,而无需暴露具体实现细节。
主要分为两种形式:

  1. 简单工厂模式:一个工厂类根据参数决定创建哪种对象。
  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
38
39
40
41
42
43
44
// 1. 定义抽象产品接口
interface Shape {
void draw();
}

// 2. 具体产品实现
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}

class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}

// 3. 简单工厂类
class ShapeFactory {
public Shape createShape(String type) {
if ("circle".equalsIgnoreCase(type)) {
return new Circle();
} else if ("rectangle".equalsIgnoreCase(type)) {
return new Rectangle();
}
throw new IllegalArgumentException("未知图形类型");
}
}

// 4. 客户端调用
public class Client {
public static void main(String[] args) {
ShapeFactory factory = new ShapeFactory();

Shape circle = factory.createShape("circle");
circle.draw(); // 输出: 绘制圆形

Shape rectangle = factory.createShape("rectangle");
rectangle.draw(); // 输出: 绘制矩形
}
}

简单工厂模式不是一个正式的设计模式,但它是工厂模式的基础。它使用一个单独的工厂类来创建不同的对象,根据传入的参数决定创建哪种类型的对象。

工厂方法模式

在简单工厂模式下,当需要新增一种产品(如新增 Triangle 三角形)时,必须修改工厂类中的 createShape() 方法,添加新的 if-else 分支,违反了开闭原则。同时,所有对象的创建逻辑集中在单个工厂类中。工厂类代码臃肿,难以维护(尤其是产品类型较多时)。

还是以创建几何图形为例子

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
// 抽象产品接口
interface Shape {
void draw();
}

// 具体产品类:圆形
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}

// 具体产品类:矩形
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}

// 抽象工厂接口
interface ShapeFactory {
Shape createShape(); // 工厂方法(由子类实现)
}

// 圆形工厂
class CircleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Circle(); // 创建具体产品
}
}

// 矩形工厂
class RectangleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Rectangle(); // 创建具体产品
}
}

public class Client {
public static void main(String[] args) {
// 使用圆形工厂创建对象
ShapeFactory circleFactory = new CircleFactory();
Shape circle = circleFactory.createShape();
circle.draw(); // 输出: 绘制圆形

// 使用矩形工厂创建对象
ShapeFactory rectangleFactory = new RectangleFactory();
Shape rectangle = rectangleFactory.createShape();
rectangle.draw(); // 输出: 绘制矩形
}
}

工厂方法模式下,新增产品只需要扩展新的工厂类即可,不需要修改原有代码。以新增绘制三角形为例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 新增三角形产品
class Triangle implements Shape {
@Override
public void draw() {
System.out.println("绘制三角形");
}
}

// 新增三角形工厂
class TriangleFactory implements ShapeFactory {
@Override
public Shape createShape() {
return new Triangle(); // 无需修改其他工厂类
}
}
特性 简单工厂模式 工厂方法模式
开闭原则 ❌ 新增产品需修改工厂类 ✅ 新增产品只需扩展新工厂类
耦合性 客户端依赖具体工厂类 客户端依赖抽象接口,与具体工厂解耦
代码维护性 工厂类臃肿,难以维护 职责分离,每个工厂类轻量
扩展性 扩展困难(需修改核心逻辑) 扩展灵活(符合开放封闭原则)
适用场景 产品类型固定且极少变化 产品类型多且可能频繁扩展

作用

  1. 解耦:客户端无需关注对象的创建过程,只需通过工厂接口获取对象。
  2. 可维护性:新增产品类时,只需扩展工厂逻辑,无需修改客户端代码。
  3. 复用性:将对象创建逻辑集中管理,避免重复代码。

使用场景

  1. 对象创建过程复杂(如依赖配置、环境参数等)。
  2. 需要统一管理对象的生命周期(如数据库连接池)。
  3. 系统需要支持多种类型的对象(如不同数据库驱动、不同主题的UI组件)。

抽象工厂模式

抽象工厂模式用于创建 多个相关或依赖的产品族,而不仅仅是单一类型的产品。它强调 产品之间的组合关系,例如“不同主题的UI组件”(如按钮、文本框、对话框等需统一风格)。

还是以绘制几何图形为例:

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
65
66
67
68
69
70
71
72
73
// 抽象产品1:形状
interface Shape {
void draw();
}

// 抽象产品2:颜色
interface Color {
void fill();
}

// 具体形状:圆形
class Circle implements Shape {
@Override
public void draw() {
System.out.println("绘制圆形");
}
}

// 具体形状:矩形
class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("绘制矩形");
}
}

// 具体颜色:红色
class Red implements Color {
@Override
public void fill() {
System.out.println("填充红色");
}
}

// 具体颜色:蓝色
class Blue implements Color {
@Override
public void fill() {
System.out.println("填充蓝色");
}
}

// 抽象工厂接口
interface AbstractFactory {
Shape createShape();
Color createColor();
}

// 具体工厂1:红色圆形风格
class RedCircleFactory implements AbstractFactory {
@Override
public Shape createShape() {
return new Circle();
}

@Override
public Color createColor() {
return new Red();
}
}

// 具体工厂2:蓝色矩形风格
class BlueRectangleFactory implements AbstractFactory {
@Override
public Shape createShape() {
return new Rectangle();
}

@Override
public Color createColor() {
return new Blue();
}
}

工厂方法模式与抽象工厂对比

工厂方法模式

扩展产品类型:新增一种产品(如 Triangle),只需添加 Triangle 类和 TriangleFactory 类,无需修改现有代码符合开闭原则

抽象工厂模式

扩展产品族:新增一个产品族(如“暗黑主题”),只需添加 DarkThemeFactory 并实现所有产品接口(DarkButtonDarkTextBox),无需修改现有代码符合开闭原则

扩展产品种类:若需新增一种产品类型(如在UI组件中新增 Checkbox),必须修改所有工厂接口(AbstractFactory)和所有具体工厂类,违反开闭原则这是抽象工厂的主要局限性

工厂方法模式 抽象工厂模式
核心思想 通过子类化实现单一产品的多态创建 通过组合多个工厂方法创建一组相关产品
设计侧重点 单一产品的扩展性 产品族的兼容性和一致性
代码复杂度 较低(仅需管理单一产品类型) 较高(需管理多个产品类型及其关系)
适用性 单一产品的多态场景 复杂系统中需要统一管理多个关联对象的场景

当系统需要 独立扩展单一类型产品的不同变体,且产品之间无强关联时(例如不同数据库驱动、不同日志输出方式)。当系统需要 确保一组相关对象必须一起使用,且需要统一管理它们的生命周期时(例如跨平台UI组件、游戏主题资源)。

单例模式

在软件开发中,某些类需要全局唯一实例以确保行为一致或节省资源。例如:

  • 配置管理类:多个实例可能导致配置冲突。
  • 数据库连接池:频繁创建连接对象会浪费资源。
  • 日志记录器:全局共享同一个日志文件句柄。

直接通过 new 创建多个实例会导致资源浪费或状态不一致。单例模式通过强制控制实例化次数解决这一问题。

单例模式(Singleton Pattern)是一种创建型设计模式,核心思想是确保一个类只有一个实例,并提供一个全局访问点
关键实现:

  1. 私有化构造函数:禁止外部直接通过 new 创建实例。
  2. 静态私有实例:类内部持有唯一实例。
  3. 静态公有方法:提供全局访问入口(如 getInstance())。

饿汉式单例

1
2
3
4
5
6
7
8
9
10
public class EagerSingleton {
// 类加载时直接初始化
private static final EagerSingleton instance = new EagerSingleton();

private EagerSingleton() {}

public static EagerSingleton getInstance() {
return instance;
}
}

饿汉式在类加载时直接初始化实例,线程安全但可能浪费资源

懒汉式单例

双重锁检查

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
public class Singleton {
// 1. 私有静态实例(volatile 保证可见性和禁止指令重排序)
private static volatile Singleton instance;

// 2. 私有构造函数,防止外部实例化
private Singleton() {
// 防止通过反射破坏单例
if (instance != null) {
throw new RuntimeException("禁止通过反射创建实例!");
}
}

// 3. 全局访问方法(双重检查锁定)
public static Singleton getInstance() {
if (instance == null) { // 第一次检查(避免不必要的同步)
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查(确保线程安全)
instance = new Singleton();
}
}
}
return instance;
}

// 示例方法
public void showMessage() {
System.out.println("单例对象调用成功!");
}
}

// 客户端调用
public class Client {
public static void main(String[] args) {
Singleton s1 = Singleton.getInstance();
Singleton s2 = Singleton.getInstance();

System.out.println(s1 == s2); // 输出:true(同一对象)
s1.showMessage(); // 输出:单例对象调用成功!
}
}

登记式/静态内部类

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
public class Singleton {
// 1. 私有构造函数
private Singleton() {
// 防止反射破坏单例
if (InnerHolder.INSTANCE != null) {
throw new RuntimeException("禁止通过反射创建实例!");
}
}

// 2. 静态内部类(延迟加载的核心)
private static class InnerHolder {
// 静态初始化器由 JVM 保证线程安全
private static final Singleton INSTANCE = new Singleton();
}

// 3. 全局访问方法
public static Singleton getInstance() {
return InnerHolder.INSTANCE;
}

// 示例方法
public void showMessage() {
System.out.println("静态内部类单例调用成功!");
}
}

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟饿汉式不同的是:饿汉式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。

静态内部类单例模式通过 JVM 类加载机制静态内部类的延迟加载特性,完美平衡了线程安全、延迟加载和代码简洁性。它是 Java 中实现单例模式的 推荐方式,尤其适合高并发场景。

注意事项

  1. 反射攻击:通过反射可绕过私有构造函数,需在构造器中添加防御代码。

    1
    2
    3
    4
    5
    private Singleton() {
    if (InnerHolder.INSTANCE != null) {
    throw new RuntimeException("禁止通过反射创建实例!");
    }
    }
  2. 序列化破坏:反序列化会创建新对象,需实现 readResolve() 方法返回实例。

    1
    2
    3
    private Object readResolve() {
    return InnerHolder.INSTANCE; // 直接返回单例实例,避免反序列化创建新对象
    }
  3. 多类加载器:不同类加载器可能导致多个实例,需确保类加载器唯一。

三种实现方式对比

实现方式 懒加载 线程安全 代码复杂度 防反射/序列化破坏
双重检查锁定(DCL) 高(需 volatile 和 synchronized) 可以额外处理
饿汉式
静态内部类 可以额外处理

作用

  1. 资源优化:避免重复创建对象,节省内存和初始化开销。
  2. 状态一致性:全局唯一实例保证数据和行为统一。
  3. 访问控制:提供统一的入口,便于管理依赖关系。

使用场景

  1. 共享资源管理:如线程池、缓存、文件系统。
  2. 配置信息类:全局统一的配置参数。
  3. 工具类:无状态的工具方法集合(如日期格式化)。
  4. 全局计数器:需要统一统计的场景。

建造者模式

在软件开发中,当需要创建 复杂对象(包含多个组成部分或配置参数)时,直接通过构造函数或 setter 方法会导致以下问题:

  1. 构造函数参数爆炸:对象包含大量可选参数时,构造方法需要重载多个版本(如 new Product(p1, p2, p3, ...)),代码臃肿且难以维护。
  2. 构造过程不灵活:对象的构造步骤可能需要按特定顺序执行,直接调用 setter 方法容易遗漏步骤或破坏一致性。
  3. 对象表示与构造逻辑耦合:同一对象的构造逻辑可能因场景不同而变化(如不同配置的电脑组装)。

建造者模式 通过 分离对象的构造过程与表示,提供一种 分步骤、可复用 的创建方式,解决上述问题。

建造者模式(Builder Pattern)是一种 创建型设计模式,核心思想是将 复杂对象的构建过程拆解为多个独立步骤,并由一个 指导者(Director) 统一协调构造流程。

  • 建造者(Builder):定义构造步骤的抽象接口。
  • 具体建造者(ConcreteBuilder):实现各步骤的具体逻辑,并返回最终对象。
  • 产品(Product):被构造的复杂对象。
  • 指导者(Director):按顺序调用建造者的方法,控制构造流程。

作用

  1. 解耦构造过程与对象表示:同一构造过程可创建不同配置的对象。
  2. 分步构造:支持按需设置参数,避免构造方法参数过多。
  3. 代码可维护性:新增构造逻辑只需扩展新的建造者,无需修改已有代码。
  4. 精细化控制:指导者可确保构造步骤的顺序和完整性。

使用场景

  1. 对象包含多个可选参数或复杂依赖(如配置类、HTTP 请求)。
  2. 需要不同配置的同一类对象(如不同套餐的订单、不同配置的电脑)。
  3. 构造过程需要分步骤或严格顺序(如文档生成器、游戏角色创建)。
  4. 希望隐藏对象的构造细节(如框架中复杂对象的创建)。

案例

以搭建一台计算机为例子,计算机包含必选组件(CPU、内存)和可选组件(硬盘、显卡)。

1. 定义产品类(Computer)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// 产品类
public class Computer {
private String cpu; // 必选
private String ram; // 必选
private String hdd; // 可选
private String gpu; // 可选

public Computer(String cpu, String ram) {
this.cpu = cpu;
this.ram = ram;
}

// 可选组件的 setter 方法
public void setHdd(String hdd) { this.hdd = hdd; }
public void setGpu(String gpu) { this.gpu = gpu; }

@Override
public String toString() {
return "Computer [CPU=" + cpu + ", RAM=" + ram + ", HDD=" + hdd + ", GPU=" + gpu + "]";
}
}

2. 定义抽象建造者(ComputerBuilder)

1
2
3
4
5
6
7
8
// 抽象建造者接口
public interface ComputerBuilder {
void setCPU(String cpu); // 必选步骤
void setRAM(String ram); // 必选步骤
void setHDD(String hdd); // 可选步骤
void setGPU(String gpu); // 可选步骤
Computer build(); // 返回最终产品
}

3. 实现具体建造者(GamingComputerBuilder)

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
// 具体建造者:游戏电脑配置
public class GamingComputerBuilder implements ComputerBuilder {
private Computer computer;

public GamingComputerBuilder(String baseCpu, String baseRam) {
this.computer = new Computer(baseCpu, baseRam); // 初始化必选组件
}

@Override
public void setCPU(String cpu) {
computer.setCpu(cpu);
}

@Override
public void setRAM(String ram) {
computer.setRam(ram);
}

@Override
public void setHDD(String hdd) {
computer.setHdd(hdd); // 可选:设置硬盘
}

@Override
public void setGPU(String gpu) {
computer.setGpu(gpu); // 可选:设置显卡
}

@Override
public Computer build() {
return computer; // 返回构建完成的计算机
}
}

4. 定义指导者(Director,可选)

1
2
3
4
5
6
7
8
9
10
// 指导者(统一构造流程)
public class Director {
public Computer buildGamingComputer(ComputerBuilder builder) {
builder.setCPU("Intel i9"); // 必选
builder.setRAM("32GB DDR5"); // 必选
builder.setHDD("1TB SSD"); // 可选
builder.setGPU("NVIDIA RTX 4090"); // 可选
return builder.build();
}
}

5. 客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Client {
public static void main(String[] args) {
// 方式1:直接使用建造者(无需指导者)
ComputerBuilder builder = new GamingComputerBuilder("Intel i7", "16GB DDR4");
builder.setHDD("512GB SSD");
builder.setGPU("NVIDIA RTX 3080");
Computer computer1 = builder.build();
System.out.println("自定义配置: " + computer1);

// 方式2:通过指导者构建
Director director = new Director();
Computer computer2 = director.buildGamingComputer(new GamingComputerBuilder("", ""));
System.out.println("推荐配置: " + computer2);
}
}

建造者模式通过分步骤构造复杂对象,解决了多参数、可选配置和构造顺序的难题。其核心优势在于:

  1. 灵活性:支持不同配置的对象构造。
  2. 可维护性:构造逻辑集中管理,易于扩展。
  3. 高可读性:链式调用使代码更直观。

经典应用场景包括:

  • Java 中的 StringBuilderDocumentBuilder
  • Android 中的 AlertDialog.Builder
  • Spring 中的 BeanDefinitionBuilder

与传统构造方式对比

构造方式 传统构造方法 建造者模式
多参数处理 需重载多个构造函数,代码臃肿 通过方法链式调用,灵活设置参数
可选参数 需为每个参数组合提供构造方法 按需调用 setter,避免冗余代码
构造顺序控制 无法保证步骤顺序,易遗漏必选参数 指导者可强制必选步骤的执行顺序
代码可读性 参数含义不直观(如 new Computer("i7", 16, null, "3080") 链式调用清晰(如 .setCPU("i7").setRAM(16)...

原型模式

在软件开发中,直接通过 new 创建对象可能导致以下问题:

  1. 资源消耗大:对象初始化需要复杂计算、数据库查询或网络请求,重复创建效率低下。
  2. 依赖具体类:客户端代码需硬编码类名,违反依赖倒置原则。
  3. 动态配置困难:对象需根据已有实例的状态动态调整参数,直接构造难以复用已有状态。

原型模式 通过 克隆已有对象 生成新对象,避免重复初始化过程,提升性能和灵活性。

原型模式(Prototype Pattern)是一种 创建型设计模式,核心思想是通过 复制现有实例(原型)创建新对象,而非通过 new 实例化类。

  • 原型接口:声明克隆方法(如 clone())。
  • 具体原型:实现克隆逻辑,生成自身副本。
  • 客户端:通过原型对象克隆新实例,无需依赖具体类。

作用

  1. 性能优化:绕过复杂初始化过程,直接复制已有对象状态。
  2. 动态创建对象:运行时根据原型动态生成新对象。
  3. 解耦客户端与具体类:客户端仅依赖原型接口,与具体实现解耦。
  4. 快速生成相似对象:适用于对象状态变化频繁的场景。

使用场景

  1. 对象创建成本高:如数据库连接、复杂计算结果的缓存复用。
  2. 需要隔离对象创建细节:如游戏开发中快速生成敌人、武器等实体。
  3. 动态配置对象:通过修改原型状态生成不同配置的实例。
  4. 撤销/恢复功能:保存对象历史状态(如文档编辑器的快照)。

经典简单实例

以图形编辑器中的形状克隆为例,演示原型模式的实现。

1. 定义原型接口(实现 Cloneable)

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
// 原型接口(Java 中 Cloneable 是标记接口)
public abstract class Shape implements Cloneable {
private String type;
private String color;

public Shape(String type, String color) {
this.type = type;
this.color = color;
}

// 抽象方法(由子类实现具体绘制逻辑)
public abstract void draw();

// 重写 clone() 方法实现浅拷贝
@Override
public Shape clone() {
try {
return (Shape) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("克隆失败", e);
}
}

// Getter & Setter
public String getType() { return type; }
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
}

2. 实现具体原型类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 具体原型1:圆形
public class Circle extends Shape {
public Circle(String color) {
super("Circle", color);
}

@Override
public void draw() {
System.out.println("绘制 " + getColor() + " 的圆形");
}
}

// 具体原型2:矩形
public class Rectangle extends Shape {
public Rectangle(String color) {
super("Rectangle", color);
}

@Override
public void draw() {
System.out.println("绘制 " + getColor() + " 的矩形");
}
}

3. 客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Client {
public static void main(String[] args) {
// 初始化原型对象
Shape redCircle = new Circle("红色");
Shape blueRectangle = new Rectangle("蓝色");

// 通过克隆生成新对象
Shape clonedCircle = redCircle.clone();
clonedCircle.setColor("绿色"); // 修改克隆体的颜色

Shape clonedRectangle = blueRectangle.clone();
clonedRectangle.setColor("黄色");

// 验证克隆结果
System.out.println("原始圆形颜色: " + redCircle.getColor()); // 输出: 红色
System.out.println("克隆圆形颜色: " + clonedCircle.getColor()); // 输出: 绿色

clonedCircle.draw(); // 输出: 绘制 绿色 的圆形
clonedRectangle.draw(); // 输出: 绘制 黄色 的矩形
}
}

深拷贝与浅拷贝

  • 浅拷贝:复制对象基本类型字段和引用地址,引用类型成员与原对象共享。
  • 深拷贝:完全复制对象及其引用链上的所有对象,需手动处理引用类型字段。

示例(深拷贝实现)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class ComplexShape extends Shape {
private List<String> tags;

public ComplexShape(String type, String color, List<String> tags) {
super(type, color);
this.tags = new ArrayList<>(tags); // 避免外部修改
}

@Override
public ComplexShape clone() {
ComplexShape clone = (ComplexShape) super.clone();
clone.tags = new ArrayList<>(this.tags); // 深拷贝列表
return clone;
}
}

对比其他创建型模式

模式 核心思想 适用场景
工厂方法 子类决定实例化哪个类 需要解耦对象创建与使用
抽象工厂 创建多个相关产品族 需要统一风格的多产品组合
建造者 分步骤构造复杂对象 对象参数多且构造顺序严格
原型模式 克隆已有对象生成新实例 对象创建成本高或需动态复制状态

原型模式通过克隆机制高效生成对象,尤其适用于以下场景:

  • 性能敏感:避免重复执行高成本初始化逻辑。
  • 动态配置:通过修改原型状态快速生成变体对象。
  • 隔离创建细节:客户端无需依赖具体类名。

经典应用

  • Java 的 Object.clone() 方法。
  • Spring 的 Prototype Bean 作用域。
  • 游戏开发中的敌人/道具生成。

适配器模式

在软件开发中,常会遇到 接口不兼容 的问题。例如:

  • 旧系统升级:旧代码需要调用新接口,但接口规范不匹配。
  • 整合第三方库:多个库功能相似但接口不同,难以统一调用。
  • 复用遗留代码:已有类功能可用,但接口不符合当前系统需求。

直接修改原有接口可能导致代码侵入性高、风险大。适配器模式通过 包装转换接口,在不修改原有代码的基础上实现兼容。

适配器模式(Adapter Pattern)是一种 结构型设计模式,核心思想是 将一个类的接口转换成客户端期望的另一个接口,使原本因接口不兼容而无法协同工作的类能够一起工作。

  • 目标接口(Target):客户端期望的接口。
  • 适配者类(Adaptee):需要被适配的现有类。
  • 适配器类(Adapter):实现目标接口,并包装适配者类的功能。

适配器模式分为两种实现方式:

  1. 类适配器:通过继承适配者类实现(需支持多重继承,Java 中不适用)。
  2. 对象适配器:通过组合适配者类对象实现(推荐方式)。

作用

  1. 接口兼容:解决新旧系统或不同组件之间的接口不匹配问题。
  2. 代码复用:无需修改现有代码即可复用已有功能。
  3. 解耦:客户端仅依赖目标接口,与具体实现解耦。
  4. 灵活性:可同时适配多个类或接口。

使用场景

  1. 旧系统整合:在新系统中复用遗留代码。
  2. 统一接口规范:封装多个第三方服务(如支付网关、日志组件)。
  3. 接口版本过渡:逐步替换旧接口,保持系统兼容性。
  4. 工具类适配:将不同工具类的功能转换为统一接口。

经典简单实例

1. 定义目标接口(客户端期望的电压)

1
2
3
4
// 目标接口:客户端需要的5V电压
public interface FiveVolt {
int getVolt5();
}

2. 定义适配者类(现有的欧洲220V插座)

1
2
3
4
5
6
// 适配者类:欧洲220V电源
public class EuropeanSocket {
public int getVolt220() {
return 220; // 输出220V电压
}
}

3. 实现适配器类(对象适配器方式)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 适配器类:将220V转换为5V
public class VoltageAdapter implements FiveVolt {
private EuropeanSocket socket; // 组合适配者对象

public VoltageAdapter(EuropeanSocket socket) {
this.socket = socket;
}

@Override
public int getVolt5() {
int volt220 = socket.getVolt220();
return convert(volt220); // 电压转换逻辑
}

private int convert(int volt220) {
return volt220 / 44; // 简化的转换逻辑(实际需电路设计)
}
}

4. 客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
// 现有欧洲电源
EuropeanSocket europeanSocket = new EuropeanSocket();

// 创建适配器,转换为5V
FiveVolt adapter = new VoltageAdapter(europeanSocket);

// 客户端使用统一接口
System.out.println("输出电压: " + adapter.getVolt5() + "V"); // 输出: 5V
}
}

类适配器 vs 对象适配器

特性 类适配器 对象适配器
实现方式 继承适配者类(需多重继承支持) 组合适配者类对象
灵活性 只能适配一个特定类 可适配多个类及其子类
代码侵入性 需修改适配者类的继承关系 无侵入,通过组合实现
适用语言 支持多重继承的语言(如C++) 所有面向对象语言(如Java、C#)

实际应用场景

  1. Java I/O 流
    InputStreamReaderOutputStreamWriter 是适配器,将字节流转换为字符流。

    1
    2
    3
    // 字节流 -> 字符流适配
    InputStream is = new FileInputStream("file.txt");
    Reader reader = new InputStreamReader(is, "UTF-8");
  2. 集合框架
    Arrays.asList() 将数组适配为 List 接口。

    1
    2
    String[] arr = {"A", "B", "C"};
    List<String> list = Arrays.asList(arr); // 适配器模式
  3. Spring MVC
    HandlerAdapter 将不同类型的 Controller 适配为统一的处理接口。

桥接模式

在软件开发中,当系统存在 多个独立变化的维度(如形状和颜色、设备与操作系统),若使用传统的继承机制,会导致 类爆炸(如 RedCircleBlueSquare 等组合类剧增),维护和扩展困难。例如:

  • 图形系统:形状(圆形、方形)与渲染方式(矢量、光栅)的组合。
  • 跨平台应用:功能模块(文件操作、网络请求)与操作系统(Windows、Linux)的适配。

桥接模式通过 将抽象与实现分离,允许两者独立变化,避免因组合维度增加导致的代码臃肿。

桥接模式(Bridge Pattern) 是一种 结构型设计模式,核心思想是将 抽象部分(Abstraction)实现部分(Implementation) 解耦,使它们可以独立扩展。

  • 抽象部分:定义高层次的业务逻辑(如形状的绘制流程)。
  • 实现部分:定义底层具体实现(如渲染引擎的具体算法)。
  • 桥接:通过组合而非继承,将抽象与实现关联。

作用

  1. 解耦抽象与实现:两者可独立扩展,互不影响。
  2. 减少子类数量:避免为每个维度组合创建子类。
  3. 提高灵活性:运行时动态切换实现(如切换渲染引擎)。
  4. 增强可维护性:修改一个维度不影响其他维度。

使用场景

  1. 多维度变化:系统存在多个独立变化的维度,需灵活组合。
  2. 避免继承膨胀:不希望使用多层继承导致类数量爆炸。
  3. 运行时绑定实现:需要在运行时动态切换底层实现。
  4. 跨平台开发:同一功能需适配不同操作系统或硬件设备。

经典简单实例

图形绘制系统 为例,实现形状(抽象)与渲染引擎(实现)的桥接。

1. 定义实现部分接口(渲染引擎)

1
2
3
4
5
// 实现部分接口:渲染引擎
public interface Renderer {
void renderCircle(float radius); // 渲染圆形
void renderSquare(float side); // 渲染方形
}

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
// 具体实现1:矢量渲染
public class VectorRenderer implements Renderer {
@Override
public void renderCircle(float radius) {
System.out.println("矢量渲染圆形,半径:" + radius);
}

@Override
public void renderSquare(float side) {
System.out.println("矢量渲染方形,边长:" + side);
}
}

// 具体实现2:光栅渲染
public class RasterRenderer implements Renderer {
@Override
public void renderCircle(float radius) {
System.out.println("光栅渲染圆形,半径:" + radius);
}

@Override
public void renderSquare(float side) {
System.out.println("光栅渲染方形,边长:" + side);
}
}

3. 定义抽象部分(形状)

1
2
3
4
5
6
7
8
9
10
// 抽象部分基类:形状
public abstract class Shape {
protected Renderer renderer; // 桥接关键:持有实现部分的引用

public Shape(Renderer renderer) {
this.renderer = renderer;
}

public abstract void draw(); // 绘制方法
}

4. 实现具体形状

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
// 具体抽象1:圆形
public class Circle extends Shape {
private float radius;

public Circle(Renderer renderer, float radius) {
super(renderer);
this.radius = radius;
}

@Override
public void draw() {
renderer.renderCircle(radius); // 调用实现部分的渲染逻辑
}
}

// 具体抽象2:方形
public class Square extends Shape {
private float side;

public Square(Renderer renderer, float side) {
super(renderer);
this.side = side;
}

@Override
public void draw() {
renderer.renderSquare(side); // 调用实现部分的渲染逻辑
}
}

5. 客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Client {
public static void main(String[] args) {
// 创建渲染引擎
Renderer vectorRenderer = new VectorRenderer();
Renderer rasterRenderer = new RasterRenderer();

// 桥接形状与渲染引擎
Shape circle = new Circle(vectorRenderer, 5.0f);
Shape square = new Square(rasterRenderer, 10.0f);

circle.draw(); // 输出:矢量渲染圆形,半径:5.0
square.draw(); // 输出:光栅渲染方形,边长:10.0

// 动态切换渲染引擎
circle = new Circle(rasterRenderer, 5.0f);
circle.draw(); // 输出:光栅渲染圆形,半径:5.0
}
}

实际应用场景

  1. GUI 框架
    • 抽象:窗口、按钮、菜单。
    • 实现:不同操作系统(Windows、macOS)的底层绘制。
  2. 数据库驱动
    • 抽象:SQL 操作接口。
    • 实现:MySQL、Oracle 等驱动的具体实现。
  3. 消息通知系统
    • 抽象:消息类型(文本、图片)。
    • 实现:发送渠道(邮件、短信、APP推送)。

桥接模式通过组合代替继承,将抽象与实现解耦,适用于多维度变化的系统设计。其核心优势在于:

  1. 灵活性:运行时动态绑定实现。
  2. 可扩展性:独立扩展抽象与实现部分。
  3. 简洁性:避免类数量指数级增长。

组合模式

组合模式(Composite Pattern)是一种结构型设计模式,通过将对象组织成树形结构,使客户端能够以统一的方式处理单个对象和对象组合。核心是抽象出一个公共接口,让叶子节点和组合节点具有一致的行为。

作用

  • 统一操作:客户端无需区分处理的是单个对象还是组合对象。
  • 简化代码:消除复杂的条件判断,提升代码可维护性。
  • 扩展性强:新增组件类型时符合开闭原则(无需修改现有代码)。

使用场景

  • 需要表示部分-整体层次结构的场景(如文件系统、菜单/子菜单)。
  • 希望递归处理整个结构(如计算总价格、统计文件大小)。
  • GUI开发中嵌套组件(如窗口包含面板,面板包含按钮)。
  • 组织架构管理(如部门与员工的关系)。

经典简单实例

文件系统:

类结构设计

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
// 1. 抽象组件接口(定义统一操作)
interface FileSystemComponent {
int getSize();
}

// 2. 叶子节点:文件
class File implements FileSystemComponent {
private int size;
public File(int size) { this.size = size; }
@Override
public int getSize() { return size; }
}

// 3. 复合节点:文件夹(可包含子组件)
class Folder implements FileSystemComponent {
private List<FileSystemComponent> children = new ArrayList<>();
public void add(FileSystemComponent component) {
children.add(component);
}
@Override
public int getSize() {
int total = 0;
for (FileSystemComponent child : children) {
total += child.getSize(); // 递归计算子组件大小
}
return total;
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Main {
public static void main(String[] args) {
// 创建文件
File file1 = new File(100);
File file2 = new File(200);

// 创建文件夹并添加文件
Folder folder1 = new Folder();
Folder folder2 = new Folder();
folder1.add(file1);
folder2.add(file2);
folder2.add(folder1);

// 统一调用getSize()
System.out.println("文件大小: " + file1.getSize()); // 输出 100
System.out.println("文件夹大小: " + folder2.getSize()); // 输出 300
}
}

装饰器模式

产生背景

  • 动态扩展需求:传统继承方式难以灵活地为对象添加功能(需预定义大量子类,导致“类爆炸”)。
  • 避免侵入性修改:不希望通过修改原有类结构来增强对象的行为(符合开闭原则)。
  • 运行时灵活性:需要在运行时动态地组合对象的功能(如Java IO流、GUI组件装饰)。

装饰器模式(Decorator Pattern)是一种结构型设计模式,通过将对象包装在装饰器类中动态地添加职责到对象上。
核心思想是:组合优于继承,提供比继承更灵活的扩展方式,同时保持接口一致性。

作用

  • 动态增强功能:在不修改原有代码的情况下,为对象添加新职责。
  • 避免类爆炸:通过组合替代继承,减少子类数量。
  • 保持透明性:客户端无需感知对象是否被装饰,接口保持一致。
  • 职责自由组合:多个装饰器可以嵌套使用,实现功能叠加(如加密+压缩)。

使用场景

  • 动态扩展对象功能:如为文本添加颜色、字体等样式。
  • 不可用继承时:当类被标记为final或已有复杂的继承层次。
  • 多维度功能组合:如咖啡加糖、加奶、加冰等不同组合。
  • 核心功能与辅助功能解耦:如日志记录、权限校验等横切关注点。

经典简单实例

咖啡订单系统

类结构设计

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
// 1. 抽象组件接口(定义核心功能)
interface Coffee {
String getDescription();
double getCost();
}

// 2. 具体组件:基础咖啡
class SimpleCoffee implements Coffee {
@Override
public String getDescription() { return "Simple Coffee"; }
@Override
public double getCost() { return 2.0; }
}

// 3. 装饰器抽象类(实现组件接口,持有组件对象)
abstract class CoffeeDecorator implements Coffee {
protected Coffee decoratedCoffee;
public CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; }
@Override
public String getDescription() { return decoratedCoffee.getDescription(); }
@Override
public double getCost() { return decoratedCoffee.getCost(); }
}

// 4. 具体装饰器:添加牛奶
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) { super(coffee); }
@Override
public String getDescription() { return super.getDescription() + ", Milk"; }
@Override
public double getCost() { return super.getCost() + 0.5; }
}

// 5. 具体装饰器:添加糖
class SugarDecorator extends CoffeeDecorator {
public SugarDecorator(Coffee coffee) { super(coffee); }
@Override
public String getDescription() { return super.getDescription() + ", Sugar"; }
@Override
public double getCost() { return super.getCost() + 0.2; }
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
public class Client {
public static void main(String[] args) {
// 基础咖啡
Coffee coffee = new SimpleCoffee();
System.out.println(coffee.getDescription() + " ¥" + coffee.getCost());

// 添加牛奶和糖
coffee = new MilkDecorator(coffee);
coffee = new SugarDecorator(coffee);
System.out.println(coffee.getDescription() + " ¥" + coffee.getCost());
}
}

代理模式

产生背景

  • 访问控制需求:需要限制或增强对某个对象的访问(如权限校验、延迟加载)。
  • 解耦与扩展:避免直接操作原始对象,通过中间层代理实现功能扩展或逻辑隔离。
  • 复杂操作封装:隐藏对象的创建细节或远程调用过程(如远程服务访问、数据库连接管理)。

代理模式(Proxy Pattern)是一种结构型设计模式,通过创建代理对象来控制对原始对象(目标对象)的访问。
核心思想是:代理类与目标类实现同一接口,客户端通过代理间接操作目标对象,从而在访问前后插入额外逻辑。

作用

  • 访问控制:限制客户端直接访问目标对象(如权限验证)。
  • 延迟加载:推迟高开销对象的创建(如图片加载、数据库连接)。
  • 功能增强:在不修改目标对象的情况下添加额外逻辑(如日志记录、性能监控)。
  • 简化复杂性:封装复杂操作(如远程调用、事务管理)。

使用场景

  • 远程代理:为远程服务提供本地代理(如RPC调用、Web Service)。
  • 虚拟代理:延迟资源密集型对象的初始化(如大图加载、文件下载)。
  • 保护代理:控制敏感对象的访问权限(如权限校验)。
  • 智能代理:添加隐式功能(如对象引用计数、缓存机制)。
  • 日志/监控代理:记录操作日志或监控性能指标。

经典简单实例

图片延迟加载

类结构设计

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
// 1. 抽象接口:定义图片加载行为
interface Image {
void display();
}

// 2. 真实目标类:高开销的图片加载
class RealImage implements Image {
private String filename;
public RealImage(String filename) {
this.filename = filename;
loadFromDisk(); // 模拟耗时操作
}
private void loadFromDisk() {
System.out.println("Loading image: " + filename);
}
@Override
public void display() {
System.out.println("Displaying image: " + filename);
}
}

// 3. 代理类:控制图片的延迟加载
class ProxyImage implements Image {
private String filename;
private RealImage realImage; // 延迟初始化真实对象
public ProxyImage(String filename) { this.filename = filename; }
@Override
public void display() {
if (realImage == null) {
realImage = new RealImage(filename); // 首次访问时加载
}
realImage.display();
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Client {
public static void main(String[] args) {
// 创建代理对象(此时不加载真实图片)
Image image = new ProxyImage("test.jpg");

// 第一次调用触发真实对象加载
image.display();
// 输出: Loading image: test.jpg
// Displaying image: test.jpg

// 第二次直接使用已加载对象
image.display();
// 输出: Displaying image: test.jpg
}
}

关键点

  • 接口一致性:代理与目标对象实现相同接口,客户端无感知。
  • 控制粒度:代理可决定何时创建真实对象或是否拦截请求。
  • 动态代理:通过反射机制动态生成代理(如Java的InvocationHandler、Spring AOP)。
  • 与装饰器模式区别:装饰器侧重增强功能,代理侧重控制访问。

动态代理

定义目标接口

1
2
3
4
5
// 用户服务接口
public interface UserService {
void login(String username);
void logout();
}

实现目标类

1
2
3
4
5
6
7
8
9
10
11
12
// 真实对象
public class UserServiceImpl implements UserService {
@Override
public void login(String username) {
System.out.println(username + " 登录成功");
}

@Override
public void logout() {
System.out.println("用户退出登录");
}
}

实现InvocationHandler

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;

// 代理逻辑处理器
public class LoggingHandler implements InvocationHandler {
private Object target; // 被代理的真实对象

public LoggingHandler(Object target) {
this.target = target;
}

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 前置增强:记录方法调用日志
System.out.println("[Log] 调用方法: " + method.getName());

// 调用真实对象的方法
Object result = method.invoke(target, args);

// 后置增强(此处省略)
return result;
}
}

生成动态代理对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import java.lang.reflect.Proxy;

public class Client {
public static void main(String[] args) {
// 1. 创建真实对象
UserService realService = new UserServiceImpl();

// 2. 创建InvocationHandler,关联真实对象
InvocationHandler handler = new LoggingHandler(realService);

// 3. 动态生成代理对象
UserService proxy = (UserService) Proxy.newProxyInstance(
realService.getClass().getClassLoader(), // 类加载器
realService.getClass().getInterfaces(), // 实现的接口
handler // 调用处理器
);

// 4. 通过代理对象调用方法
proxy.login("admin");
proxy.logout();
}
}

动态代理的底层机制

  • 字节码生成Proxy.newProxyInstance() 方法在运行时动态生成代理类的字节码。
  • 反射调用:通过 Method.invoke() 调用真实对象的方法。
  • 类加载器:使用目标类的类加载器加载生成的代理类。

动态代理 vs 静态代理

特性 静态代理 动态代理
代理类创建 手动编写代理类 运行时自动生成代理类
代码冗余 每个接口需单独编写代理类(类爆炸) 一个处理器可代理所有接口
灵活性 修改逻辑需修改代理类源码 通过修改 InvocationHandler 动态调整
性能开销 无反射调用,性能高 反射调用带来轻微性能损耗
适用场景 代理少量固定接口 代理多个接口或需统一处理的横切逻辑

外观模式

外观模式(Facade Pattern)隐藏系统的复杂性,并向客户端提供了一个客户端可以访问系统的接口。这种类型的设计模式属于结构型模式,它向现有的系统添加一个接口,来隐藏系统的复杂性。

这种模式涉及到一个单一的类,该类提供了客户端请求的简化方法和对现有系统类方法的委托调用。核心思想是:封装交互细节,让客户端只需与外观对象交互,降低客户端与子系统的耦合度。

作用

  • 简化接口:将多个子系统的复杂操作封装为简单的高层接口。
  • 降低耦合:客户端仅依赖外观类,避免直接调用子系统。
  • 提高安全性:限制客户端直接访问敏感子系统。
  • 增强灵活性:子系统的内部变化不影响客户端(外观类隔离变化)。

使用场景

  • 多层系统调用:需整合多个子系统完成一个功能(如电商下单涉及库存、支付、物流)。
  • 复杂子系统封装:隐藏技术细节,提供“一键式”操作(如操作系统启动、智能家居控制)。
  • 分层架构设计:为不同层次的模块提供统一入口(如API网关封装微服务调用)。
  • 遗留系统改造:通过外观类包装旧系统接口,提供现代化访问方式。

经典简单实例

我们将创建一个 Shape 接口和实现了 Shape 接口的实体类。下一步是定义一个外观类 ShapeMaker

ShapeMaker 类使用实体类来代表用户对这些类的调用。客户端使用 ShapeMaker 类来显示结果。

类结构设计

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
// 抽象接口
public interface Shape {
void draw();
}

// 实体类
public class Rectangle implements Shape {
@Override
public void draw() {
System.out.println("Rectangle::draw()");
}
}

// 实体类
public class Square implements Shape {

@Override
public void draw() {
System.out.println("Square::draw()");
}
}

// 实体类
public class Circle implements Shape {

@Override
public void draw() {
System.out.println("Circle::draw()");
}
}

// 外观类
public class ShapeMaker {
private Shape circle;
private Shape rectangle;
private Shape square;

public ShapeMaker() {
circle = new Circle();
rectangle = new Rectangle();
square = new Square();
}

public void drawCircle(){
circle.draw();
}
public void drawRectangle(){
rectangle.draw();
}
public void drawSquare(){
square.draw();
}
}

客户端调用

1
2
3
4
5
6
7
8
9
public class Client {
public static void main(String[] args) {
ShapeMaker shapeMaker = new ShapeMaker();

shapeMaker.drawCircle();
shapeMaker.drawRectangle();
shapeMaker.drawSquare();
}
}

优缺点

优点 缺点
简化客户端调用逻辑 外观类可能成为“上帝类”
提高子系统的独立性和可维护性 新增功能需修改外观类
符合迪米特法则(最少知识原则) 无法阻止客户端直接访问子系统

外观模式通过“化繁为简”的设计理念,将复杂系统的操作收敛到一个统一入口,是降低系统使用复杂度的经典解决方案。

享元模式

产生背景

  • 资源消耗问题:系统中存在大量相似对象时(如文本编辑器中的字符、游戏中的粒子效果),重复创建会占用大量内存。
  • 重复对象冗余:许多对象的内部状态(固有属性)相同,只有少量外部状态(上下文信息)不同。
  • 性能优化需求:希望通过共享技术减少内存占用,提升系统性能(如数据库连接池、线程池)。

享元模式(Flyweight Pattern)主要用于减少创建对象的数量,以减少内存占用和提高性能。这种类型的设计模式属于结构型模式,它提供了减少对象数量从而改善应用所需的对象结构的方式。核心思想是:分离内部状态(可共享)与外部状态(不可共享),通过共享相同内部状态的对象,减少系统资源消耗。

作用

  • 减少内存占用:复用已有对象,避免重复创建。
  • 提升性能:降低垃圾回收频率和对象初始化开销。
  • 简化对象管理:通过工厂类统一管理共享对象。
  • 支持大规模对象集合:适用于需要处理成千上万相似对象的场景。

使用场景

  • 大量相似对象存在:如文档中的字符、棋局中的棋子、游戏中的子弹。
  • 对象的大部分状态可外部化:仅有少量状态需要运行时传入。
  • 需要缓冲池的场景:如数据库连接池、线程池。
  • 不可变对象复用:如Java中的String常量池、Integer缓存池(-128~127)。

经典简单实例

文字编辑器字符对象

场景描述

文字编辑器需要渲染大量字符(如字母、数字),每个字符的字体、颜色、大小等属性(内部状态)相同,但位置坐标(外部状态)不同。通过享元模式共享字符对象,避免为每个字符重复存储样式信息。

类结构设计

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
// 1. 享元接口:定义操作外部状态的方法
interface Character {
void display(int x, int y); // 外部状态:位置坐标
}

// 2. 具体享元类:存储内部状态
class ConcreteCharacter implements Character {
private String charValue; // 内部状态(不可变)
private String font; // 内部状态
private int size; // 内部状态

public ConcreteCharacter(String charValue, String font, int size) {
this.charValue = charValue;
this.font = font;
this.size = size;
}

@Override
public void display(int x, int y) {
System.out.printf("显示字符 [%s](字体:%s,大小:%d)在位置 (%d, %d)\n",
charValue, font, size, x, y);
}
}

// 3. 享元工厂:管理共享对象池
class CharacterFactory {
private static Map<String, Character> pool = new HashMap<>();

public static Character getCharacter(String charValue, String font, int size) {
String key = charValue + font + size; // 唯一标识内部状态
if (!pool.containsKey(key)) {
pool.put(key, new ConcreteCharacter(charValue, font, size));
}
return pool.get(key);
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
// 获取共享字符对象(内部状态相同则复用)
Character a1 = CharacterFactory.getCharacter("A", "宋体", 12);
Character a2 = CharacterFactory.getCharacter("A", "宋体", 12); // 复用对象
Character b = CharacterFactory.getCharacter("B", "黑体", 14);

// 使用不同外部状态(位置)
a1.display(10, 20);
a2.display(30, 40);
b.display(50, 60);
}
}

JDK中的经典应用

  • **Integer.valueOf(int)**:缓存-128~127的Integer对象(通过IntegerCache)。

    1
    2
    3
    Integer a = Integer.valueOf(127); // 从缓存获取
    Integer b = Integer.valueOf(127); // 复用对象
    System.out.println(a == b); // 输出 true
  • String常量池:相同字符串字面量指向同一内存地址。

  • **Java.lang.ThreadLocal**:线程级别的享元模式实现。

优缺点

优点 缺点
显著减少内存占用 增加系统复杂度(需区分内外状态)
提升系统性能 需要额外逻辑管理外部状态
支持大规模对象集合 可能引入线程安全问题

关键点

  • 状态分离
    • 内部状态(Intrinsic State):可共享的固有属性(如字符样式)。
    • 外部状态(Extrinsic State):不可共享的上下文信息(如位置坐标)。
  • 线程安全问题:享元对象需设计为不可变,否则共享时需同步控制。
  • 工厂管理:通过工厂类集中创建和缓存享元对象。
  • 与对象池区别:对象池管理生命周期,享元模式侧重状态共享。

对比其他模式

模式 核心目的 典型场景
享元模式 减少内存消耗,共享对象 字符渲染、粒子效果
单例模式 确保类只有一个实例 全局配置、日志对象
原型模式 通过克隆快速创建对象 复杂对象初始化优化

享元模式通过“共享”与“复用”的核心理念,成为优化高并发、大规模对象场景的利器,是性能敏感型系统设计的必备工具。

责任链模式

产生背景

  • 多条件处理需求:一个请求需要经过多个对象的处理,但具体由哪个对象处理在运行时才能确定(如审批流程、异常处理链)。
  • 解耦请求与处理:避免请求发送者与多个处理者直接耦合,提升代码灵活性和可扩展性。
  • 动态调整流程:需要灵活添加、删除或调整处理节点的顺序(如中间件拦截器链)。

责任链模式(Chain of Responsibility Pattern)是一种行为型设计模式,通过将多个处理对象连接成一条链,使请求沿着链传递,直到被某个对象处理为止。
核心思想是:每个处理对象持有下一个处理对象的引用,形成链条,请求在链上传递,处理者自行决定是否处理或转发请求。

作用

  • 解耦请求与处理:发送者无需知道具体处理者。
  • 动态组合处理流程:可灵活调整链中节点的顺序或增减节点。
  • 增强扩展性:新增处理者无需修改现有代码(符合开闭原则)。
  • 多级处理机制:允许多个对象协作处理同一请求(如多层权限校验)。

使用场景

  • 多级审批流程:如请假审批(组长→经理→HR)。
  • 请求过滤链:如Web请求处理中的权限验证、日志记录、参数校验。
  • 异常处理:逐层捕获异常(方法级→类级→全局)。
  • 日志级别处理:不同日志级别(DEBUG < INFO < ERROR)由不同处理器处理。
  • 游戏事件传播:如点击事件被UI元素逐层处理。

经典简单实例

请假审批系统

场景描述

员工提交请假申请,根据天数由不同级别的领导审批:

  • ≤3天:组长审批
  • ≤7天:经理审批
  • >7天:HR审批
    使用责任链模式实现多级审批流程。

类结构设计

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
// 1. 抽象处理者接口
interface Approver {
void setNextApprover(Approver nextApprover); // 设置下一个处理者
void handleRequest(int days); // 处理请求
}

// 2. 具体处理者:组长
class TeamLeader implements Approver {
private Approver nextApprover;

@Override
public void setNextApprover(Approver nextApprover) {
this.nextApprover = nextApprover;
}

@Override
public void handleRequest(int days) {
if (days <= 3) {
System.out.println("组长批准 " + days + " 天假期");
} else if (nextApprover != null) {
nextApprover.handleRequest(days); // 转交下一级
}
}
}

// 3. 具体处理者:经理
class Manager implements Approver {
private Approver nextApprover;

@Override
public void setNextApprover(Approver nextApprover) {
this.nextApprover = nextApprover;
}

@Override
public void handleRequest(int days) {
if (days <= 7) {
System.out.println("经理批准 " + days + " 天假期");
} else if (nextApprover != null) {
nextApprover.handleRequest(days);
}
}
}

// 4. 具体处理者:HR
class HR implements Approver {
@Override
public void setNextApprover(Approver nextApprover) {
// HR是链条终点,无需设置下一级
}

@Override
public void handleRequest(int days) {
System.out.println("HR批准 " + days + " 天假期");
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Main {
public static void main(String[] args) {
// 1. 构建责任链:组长→经理→HR
Approver teamLeader = new TeamLeader();
Approver manager = new Manager();
Approver hr = new HR();

teamLeader.setNextApprover(manager);
manager.setNextApprover(hr);

// 2. 提交不同天数的请假申请
teamLeader.handleRequest(2); // 组长处理
teamLeader.handleRequest(5); // 经理处理
teamLeader.handleRequest(10); // HR处理
}
}

关键点

  • 链条构建方式:可在客户端动态组装链,也可通过配置文件定义。
  • 终止条件:需明确链条终点(如示例中的HR节点)。
  • 纯与不纯责任链
    • 纯责任链:请求必须被某个处理者处理(如HTTP请求必须响应)。
    • 不纯责任链:请求可能未被处理(如示例中的请假审批)。
  • 性能考量:长链可能导致请求传递时间增加。

优缺点

优点 缺点
解耦请求发送者与处理者 请求可能未被处理(需兜底逻辑)
动态增删处理节点 长链影响性能
符合单一职责原则 调试复杂度增加

责任链模式通过解耦处理逻辑与执行顺序,为复杂校验场景提供了灵活、可扩展的解决方案。

实际应用

  • Java Servlet Filter:过滤器链依次处理HTTP请求。
  • Spring Security:认证与授权过滤器链。
  • 日志框架Log4j/LogbackAppender链式处理日志事件。
  • 游戏引擎:事件处理链(如点击事件由UI组件逐层处理)。

命令模式

产生背景

  • 解耦请求与执行:需要将操作请求的发起者(如按钮点击)与具体执行者(如设备操作)分离,避免直接依赖。
  • 支持撤销/重做:需要记录操作历史,实现事务回滚或操作回退(如文档编辑器的撤销功能)。
  • 灵活扩展操作:希望动态添加新命令或批量执行多个命令(如宏命令、任务队列)。

命令模式(Command Pattern)是一种行为型设计模式,通过将请求封装为独立的对象,使得可以用不同的请求参数化其他对象,并支持请求的排队、日志记录、撤销/重做等操作。核心思想:将“操作请求”抽象为对象,解耦调用者与接收者。

作用

  • 解耦调用者与接收者:调用者无需知道具体执行逻辑。
  • 支持事务操作:实现命令的撤销(Undo)、重做(Redo)。
  • 灵活组合命令:支持批量执行、延迟执行或日志记录。
  • 扩展性强:新增命令无需修改现有代码(符合开闭原则)。

使用场景

  • GUI操作:如菜单项点击、按钮事件绑定不同功能。
  • 事务管理:数据库事务的提交与回滚。
  • 任务队列/线程池:将任务封装为命令对象提交执行。
  • 遥控器/智能家居:统一控制多个设备的开关。
  • 游戏开发:技能释放、操作回放。

经典简单实例

文本编辑器撤销操作

设计一个简易文本编辑器,支持以下功能:

  1. 用户输入文字时,内容实时更新。
  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
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
65
66
import java.util.Stack;

// 1. 命令接口
interface TextCommand {
void execute();
void undo();
}

// 2. 具体命令:添加文字
class AddTextCommand implements TextCommand {
private Document document; // 接收者
private String text; // 操作内容
private int position; // 插入位置(简化场景,始终追加到末尾)

public AddTextCommand(Document document, String text) {
this.document = document;
this.text = text;
this.position = document.getContent().length(); // 记录插入位置
}

@Override
public void execute() {
document.addText(text);
}

@Override
public void undo() {
document.deleteText(position, text.length());
}
}

// 3. 接收者:文档类
class Document {
private StringBuilder content = new StringBuilder();

public void addText(String text) {
content.append(text);
System.out.println("文档内容: " + content);
}

public void deleteText(int start, int length) {
content.delete(start, start + length);
System.out.println("撤销后内容: " + content);
}

public String getContent() {
return content.toString();
}
}

// 4. 调用者:编辑历史管理器
class EditHistory {
private Stack<TextCommand> history = new Stack<>();

public void executeCommand(TextCommand command) {
command.execute();
history.push(command);
}

public void undoLastCommand() {
if (!history.isEmpty()) {
TextCommand lastCommand = history.pop();
lastCommand.undo();
}
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Client {
public static void main(String[] args) {
Document doc = new Document();
EditHistory history = new EditHistory();

// 用户输入操作
history.executeCommand(new AddTextCommand(doc, "Hello"));
history.executeCommand(new AddTextCommand(doc, " World!"));
history.executeCommand(new AddTextCommand(doc, " 你好!"));

// 连续撤销两次
history.undoLastCommand(); // 撤销 " 你好!"
history.undoLastCommand(); // 撤销 " World!"
}
}

优缺点

优点 缺点
解耦操作请求与实现 每个命令需单独定义类,可能增加代码量
支持撤销/重做、事务操作 复杂撤销逻辑需额外状态管理
易于扩展新命令

解释器模式

产生背景

  • 特定语法解释需求:需要处理自定义语法或规则(如数学表达式、正则表达式、领域特定语言),并将其转换为可执行的操作。
  • 频繁解析场景:系统中存在大量需要动态解析的语句,传统硬编码解析方式难以维护和扩展。
  • 模块化解耦诉求:希望将语法规则分解为独立模块,提升代码复用性和可维护性。

解释器模式(Interpreter Pattern)是一种行为型设计模式,通过定义语言的文法规则,并构建解释器来解释语言中的句子。核心思想:将语法规则表示为类层次结构,通过组合这些类实现语法解析。

作用

  • 灵活解析语法:将复杂语法拆解为简单规则,逐层解释。
  • 易于扩展语法:新增语法规则只需添加新类,符合开闭原则。
  • 增强可读性:通过类结构直观表达语法逻辑。
  • 统一处理流程:为不同语法元素提供一致的解析接口。

使用场景

  • 数学表达式计算:如四则运算、布尔表达式求值。
  • 规则引擎:业务规则动态解析(如促销折扣规则)。
  • 脚本语言解释器:如自定义配置脚本、游戏技能脚本。
  • SQL/正则表达式解析:将字符串转换为可执行指令。
  • 编译器设计:构建抽象语法树(AST)进行代码分析。

经典简单实例

权限检查系统

设计一个权限检查系统,根据用户属性动态解析规则,例如:

  • "role:admin" → 用户角色是否为管理员
  • "department:finance AND level >= 5" → 用户部门是财务部且级别≥5
  • "role:editor OR (department:marketing AND level > 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
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
import java.util.Map;

// 1. 抽象表达式接口
interface PermissionExpression {
boolean interpret(Map<String, String> context); // 上下文含用户信息
}

// 2. 终结符表达式:基础条件(如角色、部门)
class TerminalExpression implements PermissionExpression {
private String key;
private String value;

public TerminalExpression(String rule) {
String[] parts = rule.split(":"); // 解析"key:value"
this.key = parts[0].trim();
this.value = parts[1].trim();
}

@Override
public boolean interpret(Map<String, String> context) {
return context.get(key) != null && context.get(key).equals(value);
}
}

// 3. 非终结符表达式:AND逻辑
class AndExpression implements PermissionExpression {
private PermissionExpression expr1;
private PermissionExpression expr2;

public AndExpression(PermissionExpression expr1, PermissionExpression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}

@Override
public boolean interpret(Map<String, String> context) {
return expr1.interpret(context) && expr2.interpret(context);
}
}

// 4. 非终结符表达式:OR逻辑
class OrExpression implements PermissionExpression {
private PermissionExpression expr1;
private PermissionExpression expr2;

public OrExpression(PermissionExpression expr1, PermissionExpression expr2) {
this.expr1 = expr1;
this.expr2 = expr2;
}

@Override
public boolean interpret(Map<String, String> context) {
return expr1.interpret(context) || expr2.interpret(context);
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// 客户端构建规则树(此处简化解析过程)
public class Client {
public static void main(String[] args) {
// 用户上下文数据
Map<String, String> user = Map.of(
"role", "editor",
"department", "marketing",
"level", "4"
);

// 手动构建规则树:role:editor OR (department:marketing AND level > 3)
PermissionExpression rule = new OrExpression(
new TerminalExpression("role:editor"),
new AndExpression(
new TerminalExpression("department:marketing"),
new TerminalExpression("level:4") // 实际需扩展比较操作符解析
)
);

System.out.println("权限检查结果: " + rule.interpret(user)); // 输出 true
}
}

迭代器模型

产生背景

  • 遍历需求多样化:集合对象(如列表、树、图)的遍历方式复杂多变(顺序遍历、逆序遍历、层级遍历),硬编码遍历逻辑会导致代码臃肿。
  • 解耦诉求:希望将集合的存储结构与遍历逻辑分离,避免客户端直接操作集合内部数据。
  • 统一访问接口:为不同类型的集合(数组、链表、哈希表)提供一致的遍历方式,简化客户端调用。

迭代器模式(Iterator Pattern)是一种行为型设计模式,通过提供一种顺序访问聚合对象元素的方法,且不暴露其内部结构
核心思想:将遍历行为抽象为独立迭代器对象,让客户端通过统一接口遍历不同集合。

作用

  • 解耦集合与遍历逻辑:集合专注存储,迭代器专注遍历。
  • 支持多种遍历方式:可为同一集合提供不同迭代器(如正序、逆序)。
  • 简化集合接口:集合只需提供创建迭代器的方法,无需暴露内部结构。
  • 符合单一职责原则:分离数据存储与数据遍历职责。

使用场景

  • 封装复杂数据结构:如树形结构、图结构的遍历。
  • 统一遍历接口:让客户端以相同方式处理不同集合(如Java的Iterable接口)。
  • 延迟加载遍历:按需获取元素(如数据库查询结果分页遍历)。
  • 并行遍历支持:允许多个迭代器同时遍历同一集合。

经典简单实例

场景描述

设计一个书籍集合类 BookShelf,内部使用数组存储书籍名称。通过迭代器模式实现遍历,隐藏底层数组结构。

类结构设计

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
// 1. 迭代器接口(定义遍历方法)
interface Iterator<T> {
boolean hasNext();
T next();
}

// 2. 集合接口(定义创建迭代器的方法)
interface Aggregate<T> {
Iterator<T> createIterator();
}

// 3. 具体集合类:书架
class BookShelf implements Aggregate<String> {
private String[] books;
private int size = 0;

public BookShelf(int capacity) {
books = new String[capacity];
}

public void addBook(String book) {
if (size < books.length) {
books[size++] = book;
}
}

@Override
public Iterator<String> createIterator() {
return new BookShelfIterator();
}

// 4. 具体迭代器(内部类,封装遍历逻辑)
private class BookShelfIterator implements Iterator<String> {
private int index = 0;

@Override
public boolean hasNext() {
return index < size;
}

@Override
public String next() {
return books[index++];
}
}
}

客户端遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
BookShelf shelf = new BookShelf(3);
shelf.addBook("Design Patterns");
shelf.addBook("Clean Code");
shelf.addBook("Refactoring");

Iterator<String> it = shelf.createIterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}

关键点

  • 双重抽象
    • 集合接口(如Aggregate)定义创建迭代器的方法。
    • 迭代器接口(如Iterator)定义遍历方法(hasNextnext)。
  • 封装遍历细节:迭代器内部持有集合的引用,并实现遍历逻辑(如数组索引递增)。
  • 线程安全:多个迭代器可独立遍历同一集合(每个迭代器维护自己的遍历状态)。

实际应用

  • Java集合框架ListSet等集合通过iterator()方法返回迭代器。
  • 数据库游标:逐行遍历查询结果。
  • 文件逐行读取BufferedReaderlines()方法返回行迭代器。
  • 树结构遍历:提供深度优先、广度优先等不同迭代器。

优缺点

优点 缺点
解耦集合与遍历逻辑 增加类的数量(迭代器类)
支持多种遍历策略 简单集合使用迭代器可能过度设计
隐藏集合内部实现细节

中介者模式

产生背景

  • 复杂对象交互:当多个对象之间存在大量直接通信(如GUI组件联动、多模块协作),对象间耦合度高,形成网状依赖结构,难以维护和扩展。
  • 协调逻辑分散:对象间的交互逻辑分散在各个类中,导致代码重复且难以统一管理。
  • 系统重构困难:新增或修改一个对象可能引发多个相关类的连锁改动,违背开闭原则。

中介者模式(Mediator Pattern)是一种行为型设计模式,通过引入中介对象封装一组对象之间的交互,使其各对象不再显式相互引用,从而将网状依赖变为星型结构。核心思想:将多对多通信转化为一对多通信,中介者成为交互的中心枢纽。

作用

  • 解耦对象关系:对象间不再直接依赖,只需与中介者交互。
  • 集中控制逻辑:交互规则统一维护在中介者中,便于修改和扩展。
  • 简化对象职责:各对象只需关注自身核心逻辑,无需处理协调任务。
  • 提升可维护性:新增对象只需与中介者交互,不影响其他对象。

使用场景

  • GUI组件交互:如表单控件联动(选择省份后更新城市列表)。
  • 聊天室系统:用户通过聊天室服务器转发消息,而非直接互发。
  • 航班调度系统:协调飞机、跑道、塔台之间的通信。
  • 多模块协作:如电商系统中订单、库存、支付模块的交互。
  • 游戏角色交互:玩家、NPC、场景元素通过游戏引擎中介通信。

经典简单实例

聊天室系统

实现一个聊天室系统,多个用户(User)发送消息时,不直接相互调用,而是通过中介者(ChatRoom)统一广播消息。

类结构设计

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
// 1. 中介者接口(定义通信方法)
interface ChatMediator {
void sendMessage(String message, User user);
void addUser(User user);
}

// 2. 具体中介者:聊天室
class ChatRoom implements ChatMediator {
private List<User> users = new ArrayList<>();

@Override
public void addUser(User user) {
users.add(user);
}

@Override
public void sendMessage(String message, User sender) {
// 广播消息给其他用户(排除发送者自身)
for (User user : users) {
if (user != sender) {
user.receive(message);
}
}
}
}

// 3. 抽象同事类(用户)
abstract class User {
protected ChatMediator mediator;
protected String name;

public User(ChatMediator mediator, String name) {
this.mediator = mediator;
this.name = name;
}

public abstract void send(String message);
public abstract void receive(String message);
}

// 4. 具体同事类:普通用户
class ChatUser extends User {
public ChatUser(ChatMediator mediator, String name) {
super(mediator, name);
}

@Override
public void send(String message) {
System.out.println(name + " 发送消息: " + message);
mediator.sendMessage(message, this); // 通过中介者转发
}

@Override
public void receive(String message) {
System.out.println(name + " 收到消息: " + message);
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Client {
public static void main(String[] args) {
// 创建中介者(聊天室)
ChatMediator chatRoom = new ChatRoom();

// 创建用户并加入聊天室
User alice = new ChatUser(chatRoom, "Alice");
User bob = new ChatUser(chatRoom, "Bob");
User charlie = new ChatUser(chatRoom, "Charlie");
chatRoom.addUser(alice);
chatRoom.addUser(bob);
chatRoom.addUser(charlie);

// Alice发送消息,Bob和Charlie接收
alice.send("大家好!");
}
}

关键点

  • 中介者中心化:所有通信必须经过中介者,对象间无直接依赖。
  • 星型拓扑结构:对象与中介者形成一对多关系(对比原始网状结构)。
  • 扩展性:新增用户只需注册到中介者,无需修改现有用户代码。
  • 与观察者模式区别:观察者模式侧重一对多通知,中介者模式侧重多对多协调。

实际应用

  • Java SwingDialog 作为中介者协调按钮、输入框等组件。
  • Spring MVCDispatcherServlet 协调控制器、视图解析器、处理器映射。
  • 航空调度系统:塔台协调飞机起飞、降落请求。
  • 企业级消息中间件:Kafka、RabbitMQ作为中介者解耦生产者和消费者。

优缺点

优点 缺点
大幅降低对象间耦合度 中介者可能成为“上帝类”
简化对象职责,提升可维护性 中介者逻辑复杂化后难以维护
支持集中式交互管理 过度使用可能导致性能瓶颈

中介者模式通过中心化协调机制,将混乱的对象交互梳理为清晰的结构,是解决复杂通信场景的经典设计模式。

备忘录模式

产生背景

  • 状态保存需求:需要记录对象在某个时刻的内部状态,以便后续可回溯(如文本编辑器的撤销操作、游戏存档)。
  • 封装性保护:直接暴露对象内部状态会破坏封装性,导致代码脆弱性和安全隐患。
  • 复杂状态管理:当对象状态变更路径复杂时,手动管理历史状态容易出错。

备忘录模式(Memento Pattern)是一种行为型设计模式,允许在不破坏对象封装性的前提下,捕获并外部化对象的内部状态,以便后续可恢复到此状态。核心思想:将状态保存与恢复职责分离,通过专门对象(备忘录)管理历史状态。

作用

  • 状态快照管理:提供系统化的状态保存/恢复机制。
  • 保持封装性:不暴露对象内部实现细节。
  • 支持撤销/重做:轻松实现多级撤销操作。
  • 简化原发器职责:状态管理逻辑由备忘录类承担。

使用场景

  • 文档/编辑器:文本内容撤销与重做。
  • 游戏开发:角色状态存档/读档。
  • 事务回滚:数据库操作失败时恢复至事务前状态。
  • 配置管理:系统配置修改后的版本回溯。
  • 绘图软件:画布操作历史记录。

经典简单实例

文本编辑器撤销功能

实现一个文本编辑器,支持以下功能:

  1. 用户输入文本内容。
  2. 每次输入后自动保存状态。
  3. Ctrl+Z 可撤销到前一个状态。

类结构设计

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
import java.util.Stack;

// 1. 原发器(Originator):需保存状态的对象
class TextEditor {
private String content; // 需保存的状态

// 创建备忘录(保存当前状态)
public TextMemento save() {
return new TextMemento(content);
}

// 从备忘录恢复状态
public void restore(TextMemento memento) {
this.content = memento.getSavedContent();
}

// 业务方法:输入内容
public void write(String newContent) {
this.content = newContent;
}

public void printContent() {
System.out.println("当前内容: " + content);
}

// 2. 备忘录(Memento):内部类封装状态
public static class TextMemento {
private final String content;

private TextMemento(String content) {
this.content = content;
}

private String getSavedContent() {
return content;
}
}
}

// 3. 管理者(Caretaker):管理备忘录栈
class HistoryManager {
private Stack<TextEditor.TextMemento> history = new Stack<>();

public void push(TextEditor.TextMemento memento) {
history.push(memento);
}

public TextEditor.TextMemento pop() {
history.pop()
return history.peek();
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class Client {
public static void main(String[] args) {
TextEditor editor = new TextEditor();
HistoryManager history = new HistoryManager();

// 第1次输入并保存
editor.write("Hello");
history.push(editor.save());
editor.printContent(); // 当前内容: Hello

// 第2次输入并保存
editor.write("Hello World");
history.push(editor.save());
editor.printContent(); // 当前内容: Hello World

// 撤销到第1次状态

editor.restore(history.pop());
editor.printContent(); // 当前内容: Hello
}
}

关键点

  • 三层角色
    • Originator(原发器):产生状态变化的对象(如TextEditor)。
    • Memento(备忘录):存储原发器状态的快照(如TextMemento)。
    • Caretaker(管理者):存储和管理备忘录栈(如HistoryManager)。
  • 封装保护:备忘录类的构造方法和状态访问权限设置为 private 或包级私有,仅允许原发器访问。
  • 状态存储策略:可使用栈实现多级撤销,或链表实现任意跳转。
  • 性能优化:对于大对象,可采用增量存储或压缩技术。

实际应用

  • IDE撤销操作:IntelliJ/VSCode的代码编辑历史回溯。
  • 游戏存档系统:保存玩家位置、装备、任务进度。
  • 浏览器回退:网页浏览历史状态管理。
  • 金融交易系统:交易失败时回滚到前一个合法状态。

优缺点

优点 缺点
完美保护对象封装性 频繁保存状态可能消耗内存
简化原发器状态管理逻辑 需要额外设计备忘录存储策略
支持灵活的历史状态管理机制

对比其他模式

模式 核心目的 典型场景
备忘录模式 对象状态保存与恢复 撤销操作、事务回滚
命令模式 封装操作为对象,支持撤销/重做 任务队列、操作历史

备忘录模式通过状态外部化存储严格的封装保护,为需要历史回溯能力的系统提供了优雅解决方案,是构建健壮撤销/重做功能的黄金标准。

观察者模式

产生背景

  • 状态同步需求:当一个对象(目标对象)的状态改变时,需要自动通知其他依赖对象(观察者),并保持状态一致(如股票价格变动通知股民)。
  • 解耦需求:避免目标对象与观察者之间的直接硬编码调用,降低系统耦合度。
  • 动态订阅机制:需要支持观察者的灵活注册与移除(如用户随时关注/取消关注公众号)。

观察者模式(Observer Pattern)是一种行为型设计模式,定义对象间的一对多依赖关系,当一个对象(Subject)状态改变时,所有依赖它的对象(Observers)自动收到通知并更新。
核心思想:状态变化通知机制 + 松耦合设计

作用

  • 解耦观察者与被观察者:Subject无需知道具体Observer类型。
  • 支持广播通信:一个Subject变化可通知多个Observer。
  • 动态订阅关系:运行时可灵活添加/删除Observer。
  • 符合开闭原则:新增Observer无需修改Subject代码。

使用场景

  • 事件驱动系统:如GUI按钮点击事件、前端框架(React/Vue)的数据响应。
  • 实时数据推送:股票行情、天气预报更新。
  • 发布-订阅模型:消息队列(Kafka、RabbitMQ)、社交媒体关注机制。
  • 状态监控:服务器健康状态检测、日志监控系统。

经典简单实例

天气站数据推送

气象站(WeatherStation)监测温度变化,实时通知多个显示设备(PhoneDisplayTVDisplay)更新温度数据。

类结构设计

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
import java.util.ArrayList;
import java.util.List;

// 1. 主题接口(Subject)
interface WeatherSubject {
void registerObserver(WeatherObserver o);
void removeObserver(WeatherObserver o);
void notifyObservers();
}

// 2. 观察者接口(Observer)
interface WeatherObserver {
void update(int temperature);
}

// 3. 具体主题:气象站
class WeatherStation implements WeatherSubject {
private int temperature;
private List<WeatherObserver> observers = new ArrayList<>();

public void setTemperature(int temp) {
this.temperature = temp;
notifyObservers(); // 温度变化时自动通知观察者
}

@Override
public void registerObserver(WeatherObserver o) {
observers.add(o);
}

@Override
public void removeObserver(WeatherObserver o) {
observers.remove(o);
}

@Override
public void notifyObservers() {
for (WeatherObserver o : observers) {
o.update(temperature);
}
}
}

// 4. 具体观察者:手机显示
class PhoneDisplay implements WeatherObserver {
@Override
public void update(int temperature) {
System.out.println("手机显示温度更新: " + temperature + "°C");
}
}

// 5. 具体观察者:电视显示
class TVDisplay implements WeatherObserver {
@Override
public void update(int temperature) {
System.out.println("电视显示温度更新: " + temperature + "°C");
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Client {
public static void main(String[] args) {
// 创建主题(气象站)
WeatherStation station = new WeatherStation();

// 创建观察者(显示设备)
WeatherObserver phone = new PhoneDisplay();
WeatherObserver tv = new TVDisplay();

// 注册观察者
station.registerObserver(phone);
station.registerObserver(tv);

// 模拟温度变化
station.setTemperature(25); // 自动通知所有观察者
station.setTemperature(30);

// 移除一个观察者
station.removeObserver(tv);
station.setTemperature(28); // 仅通知手机
}
}

关键点

  • 推拉模型选择
    • 推模式(如上例):Subject主动推送完整数据(update(temp))。
    • 拉模式:Observer收到通知后,主动从Subject拉取所需数据(update()内调用subject.getTemp())。
  • 线程安全:多线程环境下需对观察者列表进行同步控制。
  • 避免循环依赖:Observer中不宜直接反向调用Subject方法。
  • JDK内置支持:Java提供java.util.Observable类和Observer接口(已过时,建议自定义实现)。

实际应用

  • Java SwingButton.addActionListener()实现点击事件监听。
  • Spring事件机制ApplicationEventPublisher发布事件,@EventListener方法处理。
  • RxJava:基于观察者模式的响应式编程库。
  • 前端框架:Vue的v-model双向数据绑定、React的useState状态管理。

优缺点

优点 缺点
解耦生产者与消费者 观察者过多时通知效率低
支持动态订阅关系 观察者处理阻塞会导致整体延迟
符合开闭原则 不当使用可能引起内存泄漏

观察者模式通过松耦合的通知机制,为对象间动态联动提供了优雅解决方案,是事件驱动架构的基石设计模式。

状态模式

产生背景

  • 复杂状态流转:对象行为随内部状态改变而改变(如订单状态从”待支付”→”已发货”→”已完成”),传统if-elseswitch判断导致代码臃肿。
  • 可维护性问题:状态变更逻辑分散在多个方法中,新增状态需修改大量现有代码,违反开闭原则。
  • 行为动态切换:需要运行时根据状态动态调整对象行为(如电梯运行/停止/故障状态下的不同响应)。

状态模式(State Pattern)是一种行为型设计模式,允许对象在内部状态改变时改变其行为,使对象看起来像是修改了它的类。
核心思想:将状态抽象为独立类,通过委托给当前状态对象实现行为切换。

作用

  • 消除条件分支:用多态代替状态判断逻辑。
  • 集中状态逻辑:每个状态的行为封装在对应的状态类中。
  • 简化上下文类:上下文(Context)只需维护当前状态引用。
  • 易于扩展:新增状态只需添加新状态类,无需修改现有代码。

使用场景

  • 状态决定行为:如电梯、交通灯、游戏角色状态(站立/奔跑/跳跃)。
  • 复杂状态机:如订单生命周期、工作流审批。
  • 替代多层条件语句:当状态判断逻辑过于复杂时。
  • UI控件交互:按钮的禁用/悬停/点击态。

经典简单实例

电风扇档位控制

实现一个电风扇控制器,包含三个档位:

  • 关闭状态:按下按钮切换到低速档
  • 低速档:按下按钮切换到中速档
  • 中速档:按下按钮切换到高速档
  • 高速档:按下按钮关闭风扇

类结构设计

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
// 1. 状态接口
interface FanState {
void handleRequest(FanContext context);
}

// 2. 具体状态类:关闭状态
class OffState implements FanState {
@Override
public void handleRequest(FanContext context) {
System.out.println("风扇从关闭切换到低速");
context.setState(new LowState());
}
}

// 3. 具体状态类:低速状态
class LowState implements FanState {
@Override
public void handleRequest(FanContext context) {
System.out.println("风扇从低速切换到中速");
context.setState(new MediumState());
}
}

// 4. 具体状态类:中速状态
class MediumState implements FanState {
@Override
public void handleRequest(FanContext context) {
System.out.println("风扇从中速切换到高速");
context.setState(new HighState());
}
}

// 5. 具体状态类:高速状态
class HighState implements FanState {
@Override
public void handleRequest(FanContext context) {
System.out.println("风扇从高速切换到关闭");
context.setState(new OffState());
}
}

// 6. 上下文类(电风扇)
class FanContext {
private FanState currentState;

public FanContext() {
this.currentState = new OffState(); // 初始状态
}

public void setState(FanState state) {
this.currentState = state;
}

public void pressButton() {
currentState.handleRequest(this); // 委托给当前状态处理
}
}

客户端实现

1
2
3
4
5
6
7
8
9
10
public class Client {
public static void main(String[] args) {
FanContext fan = new FanContext();

fan.pressButton(); // 关闭 → 低速
fan.pressButton(); // 低速 → 中速
fan.pressButton(); // 中速 → 高速
fan.pressButton(); // 高速 → 关闭
}
}

关键点

  • 状态转换方式
    • 由Context决定(集中管理转换逻辑)。
    • 由具体状态类决定(如示例中每个状态知道下一个状态)。
    • 通过外部输入决定(如根据参数选择下一状态)。
  • 共享状态对象:若状态无实例变量,可设计为单例(如HighState.INSTANCE)。
  • 与策略模式区别:策略模式是主动切换算法,状态模式是被动响应状态变化。
优点 缺点
消除复杂条件分支 状态类数量可能过多
集中状态相关行为 状态转换逻辑分散
符合单一职责和开闭原则 小规模状态机可能过度设计

状态模式通过将状态对象化行为委托,为复杂状态管理提供了清晰的可扩展方案,是处理状态驱动型逻辑的利器。

策略模式

产生背景

  • 算法多样化需求:同一功能需要多种实现方式(如排序算法、支付方式),硬编码多种实现会导致代码臃肿。
  • 避免条件分支膨胀:使用大量if-elseswitch选择不同算法,难以维护和扩展。
  • 运行时灵活切换:需要在程序运行时动态切换算法实现(如根据性能需求切换排序策略)。

策略模式(Strategy Pattern)是一种行为型设计模式,定义一系列算法,将每个算法封装起来,并使它们可以相互替换。策略模式让算法的变化独立于使用算法的客户端。
核心思想:抽象算法接口 + 委托调用

作用

  • 解耦算法与使用方:客户端依赖抽象策略,而非具体实现。
  • 灵活扩展算法:新增算法无需修改现有代码(符合开闭原则)。
  • 消除条件语句:用多态代替条件判断。
  • 运行时动态切换:可根据需求切换不同策略。

使用场景

  • 多种算法变体:如排序(快速排序、归并排序)、压缩(ZIP、RAR)、加密(AES、DES)。
  • 支付方式选择:微信支付、支付宝、银联等。
  • 游戏技能系统:不同角色使用不同攻击策略。
  • 运费计算规则:根据地区、重量等动态选择计算方式。

经典简单实例

电商支付策略

电商系统支持多种支付方式(支付宝、微信支付、信用卡),根据不同用户选择调用不同支付接口。

类结构设计

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
// 1. 抽象策略接口
interface PaymentStrategy {
void pay(double amount);
}

// 2. 具体策略类:支付宝支付
class AlipayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用支付宝支付: " + amount + "元");
// 调用支付宝SDK...
}
}

// 3. 具体策略类:微信支付
class WechatPayStrategy implements PaymentStrategy {
@Override
public void pay(double amount) {
System.out.println("使用微信支付: " + amount + "元");
// 调用微信支付API...
}
}

// 4. 上下文类(购物车)
class ShoppingCart {
private PaymentStrategy paymentStrategy;

public void setPaymentStrategy(PaymentStrategy strategy) {
this.paymentStrategy = strategy;
}

public void checkout(double amount) {
paymentStrategy.pay(amount); // 委托给具体策略执行
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
12
13
public class Client {
public static void main(String[] args) {
ShoppingCart cart = new ShoppingCart();

// 用户选择支付宝支付
cart.setPaymentStrategy(new AlipayStrategy());
cart.checkout(100.0);

// 用户切换为微信支付
cart.setPaymentStrategy(new WechatPayStrategy());
cart.checkout(200.0);
}
}

关键点

  • 策略抽象:所有策略实现同一接口,保证可互换性。
  • 上下文委托:上下文类持有策略引用,通过委托调用具体策略。
  • 无状态策略:若策略无需维护状态,可设计为单例(如AlipayStrategy.INSTANCE)。
  • 与工厂模式结合:通过工厂创建具体策略对象,进一步解耦。
优点 缺点
灵活切换算法实现 客户端需了解不同策略差异
避免多重条件判断 策略类数量可能过多
符合开闭原则 增加对象创建开销

状态模式 vs 策略模式

模式 核心目的 典型场景
策略模式 灵活切换算法 支付方式、排序算法
状态模式 状态驱动行为变化 订单状态机、电梯控制

策略模式通过算法抽象委托调用,为灵活多变的业务需求提供了优雅的扩展方案,是应对算法变化的经典设计模式。

模板方法模式

产生背景

  • 流程标准化需求:多个相似操作具有固定流程步骤,但某些步骤的具体实现可能不同(如制作饮料的冲泡流程)。
  • 代码复用痛点:相同流程代码在多处重复,修改时需要同步更新所有副本,容易出错。
  • 扩展控制需求:需要固定核心流程架构,同时允许子类灵活扩展特定步骤。

模板方法模式(Template Method Pattern)是一种行为型设计模式,在抽象类中定义一个操作中的算法骨架,将某些步骤延迟到子类实现。核心思想:父类定义流程框架 + 子类实现可变步骤

作用

  • 封装不变流程:固定算法结构,避免重复代码。
  • 保护核心流程:通过final防止子类重写模板方法。
  • 灵活扩展:子类只需关注差异化实现。
  • 反向控制:父类调用子类操作(好莱坞原则:”Don’t call us, we’ll call you”)。

使用场景

  • 固定流程业务:如文档导出(准备数据→生成文件→上传云存储)、支付流程(验签→扣款→通知)。
  • 框架设计:Spring的JdbcTemplate、Servlet的service()方法。
  • 测试用例:测试前置准备→执行→清理的标准化流程。
  • 游戏开发:游戏角色的固定行为流程(移动→攻击→结算)。

经典简单实例

饮料制作流程

咖啡和茶的制作流程类似但存在差异:

  1. 咖啡:烧水→冲泡咖啡→加糖牛奶→倒入杯子
  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
38
39
40
41
42
43
44
45
46
47
48
49
50
// 1. 抽象模板类
abstract class BeverageTemplate {

// 模板方法(final防止子类覆盖)
public final void prepareBeverage() {
boilWater();
brew();
addCondiments();
pourInCup();
}

// 公共步骤
private void boilWater() {
System.out.println("烧水");
}

private void pourInCup() {
System.out.println("倒入杯子");
}

// 抽象方法(子类必须实现)
protected abstract void brew();
protected abstract void addCondiments();
}

// 2. 具体子类:咖啡
class Coffee extends BeverageTemplate {
@Override
protected void brew() {
System.out.println("冲泡咖啡粉");
}

@Override
protected void addCondiments() {
System.out.println("加糖和牛奶");
}
}

// 3. 具体子类:茶
class Tea extends BeverageTemplate {
@Override
protected void brew() {
System.out.println("浸泡茶叶");
}

@Override
protected void addCondiments() {
System.out.println("加柠檬片");
}
}

客户端调用

1
2
3
4
5
6
7
8
9
10
11
public class Client {
public static void main(String[] args) {
System.out.println("制作咖啡:");
BeverageTemplate coffee = new Coffee();
coffee.prepareBeverage();

System.out.println("\n制作茶:");
BeverageTemplate tea = new Tea();
tea.prepareBeverage();
}
}

关键点

  • 模板方法声明为final:保护算法结构不被子类修改。
  • 钩子方法(Hook):提供默认空实现的非抽象方法,子类可选择覆盖(如customerWantsCondiments()控制是否加调料)。
  • 好莱坞原则:父类控制流程,子类被动提供实现。
  • 与策略模式对比:策略模式通过组合切换完整算法,模板方法通过继承定制部分步骤。

实际应用

  • Java I/OInputStreamread()方法(模板方法)调用read(byte[], int, int)(子类实现)。
  • Servlet生命周期HttpServletservice()方法调度doGet()/doPost()
  • JUnit测试setUp()testXxx()tearDown()固定流程。
  • Android ActivityonCreate()onStart()onResume()生命周期模板。

优缺点

优点 缺点
提高代码复用性 每新增一个实现需创建子类
便于维护核心流程 继承关系可能带来耦合
允许精细化扩展 违反组合优于继承原则

模板方法模式通过流程模板化步骤延迟实现,为具有稳定结构但可变细节的操作提供了优雅的解决方案,是框架设计的常用模式。

访问者模式

产生背景

  • 复杂对象结构操作扩展:当系统包含复杂对象结构(如树形结构、集合元素),需要频繁添加新操作(如数据统计、格式转换),但不愿修改对象类(避免破坏开闭原则)。
  • 操作逻辑分散:若将不同操作硬编码到对象类中,会导致类职责膨胀、代码耦合度高。
  • 跨类统一处理:需对异构对象(如不同类型的UI控件)执行统一操作(如渲染、导出)。

访问者模式(Visitor Pattern)是一种行为型设计模式,允许在不修改对象结构的前提下,定义作用于这些元素的新操作
核心思想:将数据结构与数据操作分离,通过“访问者”对象集中实现跨对象的操作逻辑。

作用

  • 解耦数据结构与操作:操作逻辑集中在访问者类中。
  • 灵活扩展操作:新增操作只需添加新的访问者类。
  • 统一异构对象处理:为不同类型元素提供统一操作接口。
  • 符合开闭原则:新增操作不修改元素类,但新增元素需修改所有访问者。

使用场景

  • 复杂对象结构遍历:如编译器抽象语法树(AST)的类型检查、代码生成。
  • 跨类型统一操作:如文档导出(将段落、图片转为HTML/Markdown)。
  • 数据统计与报表:如计算电商订单总价、库存统计。
  • UI框架渲染:遍历UI控件树进行布局、绘制。

经典简单实例

购物车价格计算

购物车包含不同类型的商品(书、水果),需支持两种操作:

  1. 计算总价格
  2. 计算税金(书税率为10%,水果免税)
    通过访问者模式实现价格计算逻辑与商品类的解耦。

类结构设计

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
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 1. 元素接口(定义接受访问者的方法)
interface ItemElement {
double accept(Visitor visitor);
}

// 2. 具体元素类:书
class Book implements ItemElement {
private double price;
public Book(double price) { this.price = price; }
public double getPrice() { return price; }

@Override
public double accept(Visitor visitor) {
return visitor.visit(this); // 将自身传递给访问者
}
}

// 3. 具体元素类:水果
class Fruit implements ItemElement {
private double pricePerKg;
private double weight;
public Fruit(double pricePerKg, double weight) {
this.pricePerKg = pricePerKg;
this.weight = weight;
}
public double getTotalPrice() { return pricePerKg * weight; }

@Override
public double accept(Visitor visitor) {
return visitor.visit(this);
}
}

// 4. 访问者接口(定义访问方法)
interface Visitor {
double visit(Book book);
double visit(Fruit fruit);
}

// 5. 具体访问者:价格计算器
class PriceCalculator implements Visitor {
@Override
public double visit(Book book) {
return book.getPrice();
}
@Override
public double visit(Fruit fruit) {
return fruit.getTotalPrice();
}
}

// 6. 具体访问者:税金计算器
class TaxCalculator implements Visitor {
@Override
public double visit(Book book) {
return book.getPrice() * 0.10; // 书税10%
}
@Override
public double visit(Fruit fruit) {
return 0; // 水果免税
}
}

// 客户端调用
public class Client {
public static void main(String[] args) {
ItemElement[] items = {
new Book(100),
new Fruit(20, 2) // 单价20元/kg,重量2kg
};

Visitor priceVisitor = new PriceCalculator();
Visitor taxVisitor = new TaxCalculator();

double totalPrice = 0;
double totalTax = 0;
for (ItemElement item : items) {
totalPrice += item.accept(priceVisitor);
totalTax += item.accept(taxVisitor);
}

System.out.println("总价格: " + totalPrice); // 100 + 40 = 140
System.out.println("总税金: " + totalTax); // 10 + 0 = 10
}
}

关键点

  • 双分派(Double Dispatch):通过两次动态绑定(元素→访问者→具体方法)实现多态。
  • 元素接口稳定性:若频繁新增元素类型(如新增Electronics类),需修改所有访问者接口,破坏开闭原则。
  • 访问者状态管理:访问者可在内部累积计算结果(如总价格),无需客户端遍历时累加。
  • 与迭代器模式结合:通常需要遍历对象结构并应用访问者。

实际应用

  • Java注解处理AnnotationProcessor访问编译期注解。
  • ASM字节码框架ClassVisitor遍历并修改类结构。
  • XML解析器:访问DOM树节点生成JSON或HTML。
  • 游戏引擎:遍历场景图计算光照、碰撞检测。

优缺点

优点 缺点
集中化操作逻辑,提高可维护性 新增元素类型需修改所有访问者
轻松扩展新操作 破坏元素类的封装性(需公开内部状态)
适合稳定数据结构+频繁操作变更 增加系统复杂度

访问者模式通过操作外部化双分派机制,为复杂对象结构的操作扩展提供了独特解决方案,是处理横切关注点(Cross-Cutting Concerns)的利器。