设计模式讲的是如何写出可扩展、可读、可维护的⾼质量代码,所以,它们跟平时的编码会有直接的关系,也会直 接影响到你的开发能⼒。
什么是面向对象编程?
⾯向对象编程的英⽂缩写是 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 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
| 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("绘制矩形"); } }
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("未知图形类型"); } }
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(); } }
|
特性 |
简单工厂模式 |
工厂方法模式 |
开闭原则 |
❌ 新增产品需修改工厂类 |
✅ 新增产品只需扩展新工厂类 |
耦合性 |
客户端依赖具体工厂类 |
客户端依赖抽象接口,与具体工厂解耦 |
代码维护性 |
工厂类臃肿,难以维护 |
职责分离,每个工厂类轻量 |
扩展性 |
扩展困难(需修改核心逻辑) |
扩展灵活(符合开放封闭原则) |
适用场景 |
产品类型固定且极少变化 |
产品类型多且可能频繁扩展 |
作用
- 解耦:客户端无需关注对象的创建过程,只需通过工厂接口获取对象。
- 可维护性:新增产品类时,只需扩展工厂逻辑,无需修改客户端代码。
- 复用性:将对象创建逻辑集中管理,避免重复代码。
使用场景
- 对象创建过程复杂(如依赖配置、环境参数等)。
- 需要统一管理对象的生命周期(如数据库连接池)。
- 系统需要支持多种类型的对象(如不同数据库驱动、不同主题的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
| interface Shape { void draw(); }
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(); }
class RedCircleFactory implements AbstractFactory { @Override public Shape createShape() { return new Circle(); }
@Override public Color createColor() { return new Red(); } }
class BlueRectangleFactory implements AbstractFactory { @Override public Shape createShape() { return new Rectangle(); }
@Override public Color createColor() { return new Blue(); } }
|
工厂方法模式与抽象工厂对比
工厂方法模式
扩展产品类型:新增一种产品(如 Triangle
),只需添加 Triangle
类和 TriangleFactory
类,无需修改现有代码,符合开闭原则。
抽象工厂模式
扩展产品族:新增一个产品族(如“暗黑主题”),只需添加 DarkThemeFactory
并实现所有产品接口(DarkButton
、DarkTextBox
),无需修改现有代码,符合开闭原则。
扩展产品种类:若需新增一种产品类型(如在UI组件中新增 Checkbox
),必须修改所有工厂接口(AbstractFactory
)和所有具体工厂类,违反开闭原则。这是抽象工厂的主要局限性。
|
工厂方法模式 |
抽象工厂模式 |
核心思想 |
通过子类化实现单一产品的多态创建 |
通过组合多个工厂方法创建一组相关产品 |
设计侧重点 |
单一产品的扩展性 |
产品族的兼容性和一致性 |
代码复杂度 |
较低(仅需管理单一产品类型) |
较高(需管理多个产品类型及其关系) |
适用性 |
单一产品的多态场景 |
复杂系统中需要统一管理多个关联对象的场景 |
当系统需要 独立扩展单一类型产品的不同变体,且产品之间无强关联时(例如不同数据库驱动、不同日志输出方式)。当系统需要 确保一组相关对象必须一起使用,且需要统一管理它们的生命周期时(例如跨平台UI组件、游戏主题资源)。
单例模式
在软件开发中,某些类需要全局唯一实例以确保行为一致或节省资源。例如:
- 配置管理类:多个实例可能导致配置冲突。
- 数据库连接池:频繁创建连接对象会浪费资源。
- 日志记录器:全局共享同一个日志文件句柄。
直接通过 new
创建多个实例会导致资源浪费或状态不一致。单例模式通过强制控制实例化次数解决这一问题。
单例模式(Singleton Pattern)是一种创建型设计模式,核心思想是确保一个类只有一个实例,并提供一个全局访问点。
关键实现:
- 私有化构造函数:禁止外部直接通过
new
创建实例。
- 静态私有实例:类内部持有唯一实例。
- 静态公有方法:提供全局访问入口(如
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 { private static volatile Singleton instance;
private Singleton() { if (instance != null) { throw new RuntimeException("禁止通过反射创建实例!"); } }
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); 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 { private Singleton() { if (InnerHolder.INSTANCE != null) { throw new RuntimeException("禁止通过反射创建实例!"); } }
private static class InnerHolder { private static final Singleton INSTANCE = new Singleton(); }
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 2 3 4 5
| private Singleton() { if (InnerHolder.INSTANCE != null) { throw new RuntimeException("禁止通过反射创建实例!"); } }
|
序列化破坏:反序列化会创建新对象,需实现 readResolve()
方法返回实例。
1 2 3
| private Object readResolve() { return InnerHolder.INSTANCE; }
|
多类加载器:不同类加载器可能导致多个实例,需确保类加载器唯一。
三种实现方式对比
实现方式 |
懒加载 |
线程安全 |
代码复杂度 |
防反射/序列化破坏 |
双重检查锁定(DCL) |
✅ |
✅ |
高(需 volatile 和 synchronized) |
可以额外处理 |
饿汉式 |
❌ |
✅ |
低 |
❌ |
静态内部类 |
✅ |
✅ |
低 |
可以额外处理 |
作用
- 资源优化:避免重复创建对象,节省内存和初始化开销。
- 状态一致性:全局唯一实例保证数据和行为统一。
- 访问控制:提供统一的入口,便于管理依赖关系。
使用场景
- 共享资源管理:如线程池、缓存、文件系统。
- 配置信息类:全局统一的配置参数。
- 工具类:无状态的工具方法集合(如日期格式化)。
- 全局计数器:需要统一统计的场景。
建造者模式
在软件开发中,当需要创建 复杂对象(包含多个组成部分或配置参数)时,直接通过构造函数或 setter
方法会导致以下问题:
- 构造函数参数爆炸:对象包含大量可选参数时,构造方法需要重载多个版本(如
new Product(p1, p2, p3, ...)
),代码臃肿且难以维护。
- 构造过程不灵活:对象的构造步骤可能需要按特定顺序执行,直接调用
setter
方法容易遗漏步骤或破坏一致性。
- 对象表示与构造逻辑耦合:同一对象的构造逻辑可能因场景不同而变化(如不同配置的电脑组装)。
建造者模式 通过 分离对象的构造过程与表示,提供一种 分步骤、可复用 的创建方式,解决上述问题。
建造者模式(Builder Pattern)是一种 创建型设计模式,核心思想是将 复杂对象的构建过程拆解为多个独立步骤,并由一个 指导者(Director) 统一协调构造流程。
- 建造者(Builder):定义构造步骤的抽象接口。
- 具体建造者(ConcreteBuilder):实现各步骤的具体逻辑,并返回最终对象。
- 产品(Product):被构造的复杂对象。
- 指导者(Director):按顺序调用建造者的方法,控制构造流程。
作用
- 解耦构造过程与对象表示:同一构造过程可创建不同配置的对象。
- 分步构造:支持按需设置参数,避免构造方法参数过多。
- 代码可维护性:新增构造逻辑只需扩展新的建造者,无需修改已有代码。
- 精细化控制:指导者可确保构造步骤的顺序和完整性。
使用场景
- 对象包含多个可选参数或复杂依赖(如配置类、HTTP 请求)。
- 需要不同配置的同一类对象(如不同套餐的订单、不同配置的电脑)。
- 构造过程需要分步骤或严格顺序(如文档生成器、游戏角色创建)。
- 希望隐藏对象的构造细节(如框架中复杂对象的创建)。
案例
以搭建一台计算机为例子,计算机包含必选组件(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; }
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) { 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);
Director director = new Director(); Computer computer2 = director.buildGamingComputer(new GamingComputerBuilder("", "")); System.out.println("推荐配置: " + computer2); } }
|
建造者模式通过分步骤构造复杂对象,解决了多参数、可选配置和构造顺序的难题。其核心优势在于:
- 灵活性:支持不同配置的对象构造。
- 可维护性:构造逻辑集中管理,易于扩展。
- 高可读性:链式调用使代码更直观。
经典应用场景包括:
- Java 中的
StringBuilder
、DocumentBuilder
。
- Android 中的
AlertDialog.Builder
。
- Spring 中的
BeanDefinitionBuilder
。
与传统构造方式对比
构造方式 |
传统构造方法 |
建造者模式 |
多参数处理 |
需重载多个构造函数,代码臃肿 |
通过方法链式调用,灵活设置参数 |
可选参数 |
需为每个参数组合提供构造方法 |
按需调用 setter ,避免冗余代码 |
构造顺序控制 |
无法保证步骤顺序,易遗漏必选参数 |
指导者可强制必选步骤的执行顺序 |
代码可读性 |
参数含义不直观(如 new Computer("i7", 16, null, "3080") ) |
链式调用清晰(如 .setCPU("i7").setRAM(16)... ) |
原型模式
在软件开发中,直接通过 new
创建对象可能导致以下问题:
- 资源消耗大:对象初始化需要复杂计算、数据库查询或网络请求,重复创建效率低下。
- 依赖具体类:客户端代码需硬编码类名,违反依赖倒置原则。
- 动态配置困难:对象需根据已有实例的状态动态调整参数,直接构造难以复用已有状态。
原型模式 通过 克隆已有对象 生成新对象,避免重复初始化过程,提升性能和灵活性。
原型模式(Prototype Pattern)是一种 创建型设计模式,核心思想是通过 复制现有实例(原型)创建新对象,而非通过 new
实例化类。
- 原型接口:声明克隆方法(如
clone()
)。
- 具体原型:实现克隆逻辑,生成自身副本。
- 客户端:通过原型对象克隆新实例,无需依赖具体类。
作用
- 性能优化:绕过复杂初始化过程,直接复制已有对象状态。
- 动态创建对象:运行时根据原型动态生成新对象。
- 解耦客户端与具体类:客户端仅依赖原型接口,与具体实现解耦。
- 快速生成相似对象:适用于对象状态变化频繁的场景。
使用场景
- 对象创建成本高:如数据库连接、复杂计算结果的缓存复用。
- 需要隔离对象创建细节:如游戏开发中快速生成敌人、武器等实体。
- 动态配置对象:通过修改原型状态生成不同配置的实例。
- 撤销/恢复功能:保存对象历史状态(如文档编辑器的快照)。
经典简单实例
以图形编辑器中的形状克隆为例,演示原型模式的实现。
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
| 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();
@Override public Shape clone() { try { return (Shape) super.clone(); } catch (CloneNotSupportedException e) { throw new RuntimeException("克隆失败", e); } }
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
| public class Circle extends Shape { public Circle(String color) { super("Circle", color); }
@Override public void draw() { System.out.println("绘制 " + getColor() + " 的圆形"); } }
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):实现目标接口,并包装适配者类的功能。
适配器模式分为两种实现方式:
- 类适配器:通过继承适配者类实现(需支持多重继承,Java 中不适用)。
- 对象适配器:通过组合适配者类对象实现(推荐方式)。
作用
- 接口兼容:解决新旧系统或不同组件之间的接口不匹配问题。
- 代码复用:无需修改现有代码即可复用已有功能。
- 解耦:客户端仅依赖目标接口,与具体实现解耦。
- 灵活性:可同时适配多个类或接口。
使用场景
- 旧系统整合:在新系统中复用遗留代码。
- 统一接口规范:封装多个第三方服务(如支付网关、日志组件)。
- 接口版本过渡:逐步替换旧接口,保持系统兼容性。
- 工具类适配:将不同工具类的功能转换为统一接口。
经典简单实例
1. 定义目标接口(客户端期望的电压)
1 2 3 4
| public interface FiveVolt { int getVolt5(); }
|
2. 定义适配者类(现有的欧洲220V插座)
1 2 3 4 5 6
| public class EuropeanSocket { public int getVolt220() { return 220; } }
|
3. 实现适配器类(对象适配器方式)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| 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(); FiveVolt adapter = new VoltageAdapter(europeanSocket); System.out.println("输出电压: " + adapter.getVolt5() + "V"); } }
|
类适配器 vs 对象适配器
特性 |
类适配器 |
对象适配器 |
实现方式 |
继承适配者类(需多重继承支持) |
组合适配者类对象 |
灵活性 |
只能适配一个特定类 |
可适配多个类及其子类 |
代码侵入性 |
需修改适配者类的继承关系 |
无侵入,通过组合实现 |
适用语言 |
支持多重继承的语言(如C++) |
所有面向对象语言(如Java、C#) |
实际应用场景
Java I/O 流:
InputStreamReader
和 OutputStreamWriter
是适配器,将字节流转换为字符流。
1 2 3
| InputStream is = new FileInputStream("file.txt"); Reader reader = new InputStreamReader(is, "UTF-8");
|
集合框架:
Arrays.asList()
将数组适配为 List
接口。
1 2
| String[] arr = {"A", "B", "C"}; List<String> list = Arrays.asList(arr);
|
Spring MVC:
HandlerAdapter
将不同类型的 Controller 适配为统一的处理接口。
桥接模式
在软件开发中,当系统存在 多个独立变化的维度(如形状和颜色、设备与操作系统),若使用传统的继承机制,会导致 类爆炸(如 RedCircle
、BlueSquare
等组合类剧增),维护和扩展困难。例如:
- 图形系统:形状(圆形、方形)与渲染方式(矢量、光栅)的组合。
- 跨平台应用:功能模块(文件操作、网络请求)与操作系统(Windows、Linux)的适配。
桥接模式通过 将抽象与实现分离,允许两者独立变化,避免因组合维度增加导致的代码臃肿。
桥接模式(Bridge Pattern) 是一种 结构型设计模式,核心思想是将 抽象部分(Abstraction) 与 实现部分(Implementation) 解耦,使它们可以独立扩展。
- 抽象部分:定义高层次的业务逻辑(如形状的绘制流程)。
- 实现部分:定义底层具体实现(如渲染引擎的具体算法)。
- 桥接:通过组合而非继承,将抽象与实现关联。
作用
- 解耦抽象与实现:两者可独立扩展,互不影响。
- 减少子类数量:避免为每个维度组合创建子类。
- 提高灵活性:运行时动态切换实现(如切换渲染引擎)。
- 增强可维护性:修改一个维度不影响其他维度。
使用场景
- 多维度变化:系统存在多个独立变化的维度,需灵活组合。
- 避免继承膨胀:不希望使用多层继承导致类数量爆炸。
- 运行时绑定实现:需要在运行时动态切换底层实现。
- 跨平台开发:同一功能需适配不同操作系统或硬件设备。
经典简单实例
以 图形绘制系统 为例,实现形状(抽象)与渲染引擎(实现)的桥接。
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
| 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); } }
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
| 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); } }
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(); square.draw();
circle = new Circle(rasterRenderer, 5.0f); circle.draw(); } }
|
实际应用场景
- GUI 框架:
- 抽象:窗口、按钮、菜单。
- 实现:不同操作系统(Windows、macOS)的底层绘制。
- 数据库驱动:
- 抽象:SQL 操作接口。
- 实现:MySQL、Oracle 等驱动的具体实现。
- 消息通知系统:
- 抽象:消息类型(文本、图片)。
- 实现:发送渠道(邮件、短信、APP推送)。
桥接模式通过组合代替继承,将抽象与实现解耦,适用于多维度变化的系统设计。其核心优势在于:
- 灵活性:运行时动态绑定实现。
- 可扩展性:独立扩展抽象与实现部分。
- 简洁性:避免类数量指数级增长。
组合模式
组合模式(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
| interface FileSystemComponent { int getSize(); }
class File implements FileSystemComponent { private int size; public File(int size) { this.size = size; } @Override public int getSize() { return size; } }
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);
System.out.println("文件大小: " + file1.getSize()); System.out.println("文件夹大小: " + folder2.getSize()); } }
|
装饰器模式
产生背景
- 动态扩展需求:传统继承方式难以灵活地为对象添加功能(需预定义大量子类,导致“类爆炸”)。
- 避免侵入性修改:不希望通过修改原有类结构来增强对象的行为(符合开闭原则)。
- 运行时灵活性:需要在运行时动态地组合对象的功能(如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
| interface Coffee { String getDescription(); double getCost(); }
class SimpleCoffee implements Coffee { @Override public String getDescription() { return "Simple Coffee"; } @Override public double getCost() { return 2.0; } }
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(); } }
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; } }
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
| interface Image { void display(); }
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); } }
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(); image.display(); } }
|
关键点
- 接口一致性:代理与目标对象实现相同接口,客户端无感知。
- 控制粒度:代理可决定何时创建真实对象或是否拦截请求。
- 动态代理:通过反射机制动态生成代理(如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) { UserService realService = new UserServiceImpl();
InvocationHandler handler = new LoggingHandler(realService);
UserService proxy = (UserService) Proxy.newProxyInstance( realService.getClass().getClassLoader(), realService.getClass().getInterfaces(), handler );
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
| interface Character { void display(int x, int y); }
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); } }
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);
|
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
| interface Approver { void setNextApprover(Approver nextApprover); void handleRequest(int days); }
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); } } }
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); } } }
class HR implements Approver { @Override public void setNextApprover(Approver nextApprover) { }
@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) { Approver teamLeader = new TeamLeader(); Approver manager = new Manager(); Approver hr = new HR();
teamLeader.setNextApprover(manager); manager.setNextApprover(hr);
teamLeader.handleRequest(2); teamLeader.handleRequest(5); teamLeader.handleRequest(10); } }
|
关键点
- 链条构建方式:可在客户端动态组装链,也可通过配置文件定义。
- 终止条件:需明确链条终点(如示例中的HR节点)。
- 纯与不纯责任链:
- 纯责任链:请求必须被某个处理者处理(如HTTP请求必须响应)。
- 不纯责任链:请求可能未被处理(如示例中的请假审批)。
- 性能考量:长链可能导致请求传递时间增加。
优缺点
优点 |
缺点 |
解耦请求发送者与处理者 |
请求可能未被处理(需兜底逻辑) |
动态增删处理节点 |
长链影响性能 |
符合单一职责原则 |
调试复杂度增加 |
责任链模式通过解耦处理逻辑与执行顺序,为复杂校验场景提供了灵活、可扩展的解决方案。
实际应用
- Java Servlet Filter:过滤器链依次处理HTTP请求。
- Spring Security:认证与授权过滤器链。
- 日志框架:
Log4j
/Logback
的Appender
链式处理日志事件。
- 游戏引擎:事件处理链(如点击事件由UI组件逐层处理)。
命令模式
产生背景
- 解耦请求与执行:需要将操作请求的发起者(如按钮点击)与具体执行者(如设备操作)分离,避免直接依赖。
- 支持撤销/重做:需要记录操作历史,实现事务回滚或操作回退(如文档编辑器的撤销功能)。
- 灵活扩展操作:希望动态添加新命令或批量执行多个命令(如宏命令、任务队列)。
命令模式(Command Pattern)是一种行为型设计模式,通过将请求封装为独立的对象,使得可以用不同的请求参数化其他对象,并支持请求的排队、日志记录、撤销/重做等操作。核心思想:将“操作请求”抽象为对象,解耦调用者与接收者。
作用
- 解耦调用者与接收者:调用者无需知道具体执行逻辑。
- 支持事务操作:实现命令的撤销(Undo)、重做(Redo)。
- 灵活组合命令:支持批量执行、延迟执行或日志记录。
- 扩展性强:新增命令无需修改现有代码(符合开闭原则)。
使用场景
- 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 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;
interface TextCommand { void execute(); void undo(); }
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()); } }
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(); } }
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(); } }
|
优缺点
优点 |
缺点 |
解耦操作请求与实现 |
每个命令需单独定义类,可能增加代码量 |
支持撤销/重做、事务操作 |
复杂撤销逻辑需额外状态管理 |
易于扩展新命令 |
|
解释器模式
产生背景
- 特定语法解释需求:需要处理自定义语法或规则(如数学表达式、正则表达式、领域特定语言),并将其转换为可执行的操作。
- 频繁解析场景:系统中存在大量需要动态解析的语句,传统硬编码解析方式难以维护和扩展。
- 模块化解耦诉求:希望将语法规则分解为独立模块,提升代码复用性和可维护性。
解释器模式(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;
interface PermissionExpression { boolean interpret(Map<String, String> context); }
class TerminalExpression implements PermissionExpression { private String key; private String value;
public TerminalExpression(String rule) { String[] parts = rule.split(":"); 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); } }
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); } }
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" );
PermissionExpression rule = new OrExpression( new TerminalExpression("role:editor"), new AndExpression( new TerminalExpression("department:marketing"), new TerminalExpression("level:4") ) );
System.out.println("权限检查结果: " + rule.interpret(user)); } }
|
迭代器模型
产生背景
- 遍历需求多样化:集合对象(如列表、树、图)的遍历方式复杂多变(顺序遍历、逆序遍历、层级遍历),硬编码遍历逻辑会导致代码臃肿。
- 解耦诉求:希望将集合的存储结构与遍历逻辑分离,避免客户端直接操作集合内部数据。
- 统一访问接口:为不同类型的集合(数组、链表、哈希表)提供一致的遍历方式,简化客户端调用。
迭代器模式(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
| interface Iterator<T> { boolean hasNext(); T next(); }
interface Aggregate<T> { Iterator<T> createIterator(); }
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(); }
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
)定义遍历方法(hasNext
、next
)。
- 封装遍历细节:迭代器内部持有集合的引用,并实现遍历逻辑(如数组索引递增)。
- 线程安全:多个迭代器可独立遍历同一集合(每个迭代器维护自己的遍历状态)。
实际应用
- Java集合框架:
List
、Set
等集合通过iterator()
方法返回迭代器。
- 数据库游标:逐行遍历查询结果。
- 文件逐行读取:
BufferedReader
的lines()
方法返回行迭代器。
- 树结构遍历:提供深度优先、广度优先等不同迭代器。
优缺点
优点 |
缺点 |
解耦集合与遍历逻辑 |
增加类的数量(迭代器类) |
支持多种遍历策略 |
简单集合使用迭代器可能过度设计 |
隐藏集合内部实现细节 |
|
中介者模式
产生背景
- 复杂对象交互:当多个对象之间存在大量直接通信(如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
| interface ChatMediator { void sendMessage(String message, User user); void addUser(User user); }
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); } } } }
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); }
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.send("大家好!"); } }
|
关键点
- 中介者中心化:所有通信必须经过中介者,对象间无直接依赖。
- 星型拓扑结构:对象与中介者形成一对多关系(对比原始网状结构)。
- 扩展性:新增用户只需注册到中介者,无需修改现有用户代码。
- 与观察者模式区别:观察者模式侧重一对多通知,中介者模式侧重多对多协调。
实际应用
- Java Swing:
Dialog
作为中介者协调按钮、输入框等组件。
- Spring MVC:
DispatcherServlet
协调控制器、视图解析器、处理器映射。
- 航空调度系统:塔台协调飞机起飞、降落请求。
- 企业级消息中间件:Kafka、RabbitMQ作为中介者解耦生产者和消费者。
优缺点
优点 |
缺点 |
大幅降低对象间耦合度 |
中介者可能成为“上帝类” |
简化对象职责,提升可维护性 |
中介者逻辑复杂化后难以维护 |
支持集中式交互管理 |
过度使用可能导致性能瓶颈 |
中介者模式通过中心化协调机制,将混乱的对象交互梳理为清晰的结构,是解决复杂通信场景的经典设计模式。
备忘录模式
产生背景
- 状态保存需求:需要记录对象在某个时刻的内部状态,以便后续可回溯(如文本编辑器的撤销操作、游戏存档)。
- 封装性保护:直接暴露对象内部状态会破坏封装性,导致代码脆弱性和安全隐患。
- 复杂状态管理:当对象状态变更路径复杂时,手动管理历史状态容易出错。
备忘录模式(Memento Pattern)是一种行为型设计模式,允许在不破坏对象封装性的前提下,捕获并外部化对象的内部状态,以便后续可恢复到此状态。核心思想:将状态保存与恢复职责分离,通过专门对象(备忘录)管理历史状态。
作用
- 状态快照管理:提供系统化的状态保存/恢复机制。
- 保持封装性:不暴露对象内部实现细节。
- 支持撤销/重做:轻松实现多级撤销操作。
- 简化原发器职责:状态管理逻辑由备忘录类承担。
使用场景
- 文档/编辑器:文本内容撤销与重做。
- 游戏开发:角色状态存档/读档。
- 事务回滚:数据库操作失败时恢复至事务前状态。
- 配置管理:系统配置修改后的版本回溯。
- 绘图软件:画布操作历史记录。
经典简单实例
文本编辑器撤销功能
实现一个文本编辑器,支持以下功能:
- 用户输入文本内容。
- 每次输入后自动保存状态。
- 按
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;
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); }
public static class TextMemento { private final String content; private TextMemento(String content) { this.content = content; } private String getSavedContent() { return content; } } }
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();
editor.write("Hello"); history.push(editor.save()); editor.printContent();
editor.write("Hello World"); history.push(editor.save()); editor.printContent();
editor.restore(history.pop()); editor.printContent(); } }
|
关键点
- 三层角色:
- 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
)监测温度变化,实时通知多个显示设备(PhoneDisplay
、TVDisplay
)更新温度数据。
类结构设计
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;
interface WeatherSubject { void registerObserver(WeatherObserver o); void removeObserver(WeatherObserver o); void notifyObservers(); }
interface WeatherObserver { void update(int temperature); }
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); } } }
class PhoneDisplay implements WeatherObserver { @Override public void update(int temperature) { System.out.println("手机显示温度更新: " + temperature + "°C"); } }
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 Swing:
Button.addActionListener()
实现点击事件监听。
- Spring事件机制:
ApplicationEventPublisher
发布事件,@EventListener
方法处理。
- RxJava:基于观察者模式的响应式编程库。
- 前端框架:Vue的
v-model
双向数据绑定、React的useState
状态管理。
优缺点
优点 |
缺点 |
解耦生产者与消费者 |
观察者过多时通知效率低 |
支持动态订阅关系 |
观察者处理阻塞会导致整体延迟 |
符合开闭原则 |
不当使用可能引起内存泄漏 |
观察者模式通过松耦合的通知机制,为对象间动态联动提供了优雅解决方案,是事件驱动架构的基石设计模式。
状态模式
产生背景
- 复杂状态流转:对象行为随内部状态改变而改变(如订单状态从”待支付”→”已发货”→”已完成”),传统
if-else
或switch
判断导致代码臃肿。
- 可维护性问题:状态变更逻辑分散在多个方法中,新增状态需修改大量现有代码,违反开闭原则。
- 行为动态切换:需要运行时根据状态动态调整对象行为(如电梯运行/停止/故障状态下的不同响应)。
状态模式(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
| interface FanState { void handleRequest(FanContext context); }
class OffState implements FanState { @Override public void handleRequest(FanContext context) { System.out.println("风扇从关闭切换到低速"); context.setState(new LowState()); } }
class LowState implements FanState { @Override public void handleRequest(FanContext context) { System.out.println("风扇从低速切换到中速"); context.setState(new MediumState()); } }
class MediumState implements FanState { @Override public void handleRequest(FanContext context) { System.out.println("风扇从中速切换到高速"); context.setState(new HighState()); } }
class HighState implements FanState { @Override public void handleRequest(FanContext context) { System.out.println("风扇从高速切换到关闭"); context.setState(new OffState()); } }
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-else
或switch
选择不同算法,难以维护和扩展。
- 运行时灵活切换:需要在程序运行时动态切换算法实现(如根据性能需求切换排序策略)。
策略模式(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
| interface PaymentStrategy { void pay(double amount); }
class AlipayStrategy implements PaymentStrategy { @Override public void pay(double amount) { System.out.println("使用支付宝支付: " + amount + "元"); } }
class WechatPayStrategy implements PaymentStrategy { @Override public void pay(double amount) { System.out.println("使用微信支付: " + amount + "元"); } }
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 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
| abstract class BeverageTemplate { 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(); }
class Coffee extends BeverageTemplate { @Override protected void brew() { System.out.println("冲泡咖啡粉"); } @Override protected void addCondiments() { System.out.println("加糖和牛奶"); } }
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/O:
InputStream
的read()
方法(模板方法)调用read(byte[], int, int)
(子类实现)。
- Servlet生命周期:
HttpServlet
的service()
方法调度doGet()
/doPost()
。
- JUnit测试:
setUp()
→testXxx()
→tearDown()
固定流程。
- Android Activity:
onCreate()
→onStart()
→onResume()
生命周期模板。
优缺点
优点 |
缺点 |
提高代码复用性 |
每新增一个实现需创建子类 |
便于维护核心流程 |
继承关系可能带来耦合 |
允许精细化扩展 |
违反组合优于继承原则 |
模板方法模式通过流程模板化和步骤延迟实现,为具有稳定结构但可变细节的操作提供了优雅的解决方案,是框架设计的常用模式。
访问者模式
产生背景
- 复杂对象结构操作扩展:当系统包含复杂对象结构(如树形结构、集合元素),需要频繁添加新操作(如数据统计、格式转换),但不愿修改对象类(避免破坏开闭原则)。
- 操作逻辑分散:若将不同操作硬编码到对象类中,会导致类职责膨胀、代码耦合度高。
- 跨类统一处理:需对异构对象(如不同类型的UI控件)执行统一操作(如渲染、导出)。
访问者模式(Visitor Pattern)是一种行为型设计模式,允许在不修改对象结构的前提下,定义作用于这些元素的新操作。
核心思想:将数据结构与数据操作分离,通过“访问者”对象集中实现跨对象的操作逻辑。
作用
- 解耦数据结构与操作:操作逻辑集中在访问者类中。
- 灵活扩展操作:新增操作只需添加新的访问者类。
- 统一异构对象处理:为不同类型元素提供统一操作接口。
- 符合开闭原则:新增操作不修改元素类,但新增元素需修改所有访问者。
使用场景
- 复杂对象结构遍历:如编译器抽象语法树(AST)的类型检查、代码生成。
- 跨类型统一操作:如文档导出(将段落、图片转为HTML/Markdown)。
- 数据统计与报表:如计算电商订单总价、库存统计。
- UI框架渲染:遍历UI控件树进行布局、绘制。
经典简单实例
购物车价格计算
购物车包含不同类型的商品(书、水果),需支持两种操作:
- 计算总价格
- 计算税金(书税率为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
| interface ItemElement { double accept(Visitor visitor); }
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); } }
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); } }
interface Visitor { double visit(Book book); double visit(Fruit fruit); }
class PriceCalculator implements Visitor { @Override public double visit(Book book) { return book.getPrice(); } @Override public double visit(Fruit fruit) { return fruit.getTotalPrice(); } }
class TaxCalculator implements Visitor { @Override public double visit(Book book) { return book.getPrice() * 0.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) };
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); System.out.println("总税金: " + totalTax); } }
|
关键点
- 双分派(Double Dispatch):通过两次动态绑定(元素→访问者→具体方法)实现多态。
- 元素接口稳定性:若频繁新增元素类型(如新增
Electronics
类),需修改所有访问者接口,破坏开闭原则。
- 访问者状态管理:访问者可在内部累积计算结果(如总价格),无需客户端遍历时累加。
- 与迭代器模式结合:通常需要遍历对象结构并应用访问者。
实际应用
- Java注解处理:
AnnotationProcessor
访问编译期注解。
- ASM字节码框架:
ClassVisitor
遍历并修改类结构。
- XML解析器:访问DOM树节点生成JSON或HTML。
- 游戏引擎:遍历场景图计算光照、碰撞检测。
优缺点
优点 |
缺点 |
集中化操作逻辑,提高可维护性 |
新增元素类型需修改所有访问者 |
轻松扩展新操作 |
破坏元素类的封装性(需公开内部状态) |
适合稳定数据结构+频繁操作变更 |
增加系统复杂度 |
访问者模式通过操作外部化和双分派机制,为复杂对象结构的操作扩展提供了独特解决方案,是处理横切关注点(Cross-Cutting Concerns)的利器。