设计模式面试
设计模式面试
综述
【简单】什么是设计模式?
设计模式是软件工程中针对常见问题的可复用解决方案,是前人总结的最佳实践模板。它不是现成的代码,而是一种设计思想,指导你如何组织类和对象以解决特定场景下的问题。
【简单】有哪些经典的设计模式?⭐⭐
经典设计模式通常指 GoF(Gang of Four)《设计模式》一书中总结的 23 种模式,分为三大类:
- 创建型模式:创建型模式提供了创建对象的机制, 能够提升已有代码的灵活性和可复用性。——对象如何创建
- 单例模式 (Singleton):全局唯一实例
- 简单工厂模式 (Simple Factory):通过一个工厂类根据参数创建不同产品对象,将创建逻辑集中封装,客户端无需了解具体实现
- 工厂方法模式 (Factory Method):子类决定创建哪个对象
- 抽象工厂模式 (Abstract Factory):创建相关或依赖的产品族
- 建造者模式 (Builder):分步构建复杂对象
- 原型模式 (Prototype):克隆生成对象
- 结构型模式:结构型模式介绍如何将对象和类组装成较大的结构, 并同时保持结构的灵活和高效。——对象如何组合
- 代理模式 (Proxy):控制对象访问
- 装饰模式 (Decorator):动态添加职责
- 适配器模式 (Adapter):接口转换,兼容不匹配类
- 桥接模式 (Bridge):抽象与实现分离,独立变化
- 组合模式 (Composite):树形结构表示整体-部分
- 外观模式 (Facade):为子系统提供统一接口
- 享元模式 (Flyweight):共享细粒度对象,节省内存
- 行为型模式:行为模式负责对象间的高效沟通和职责委派。——对象如何协作
- 模板方法模式 (Template Method):算法骨架,子类实现步骤
- 策略模式 (Strategy):算法可替换
- 观察者模式 (Observer):一对多通知依赖者
- 状态模式 (State):状态改变行为
- 职责链模式 (Chain of Responsibility):请求沿链传递,多处理器
- 命令模式 (Command):请求封装为对象,支持操作队列
- 迭代器模式 (Iterator):顺序访问聚合元素
- 中介者模式 (Mediator):封装对象间交互,降低耦合
- 访问者模式 (Visitor):在不改变元素类前提下增加新操作
- 备忘录模式 (Memento):保存和恢复对象状态
- 解释器模式 (Interpreter):定义并解释文法
【简单】什么是面向对象五大原则(SOLID)?⭐
- 单一职责:一个类只做一件事
- 开闭原则:扩展开放,修改关闭
- 里氏替换:子类可替换父类
- 接口隔离:接口最小化,不依赖无用方法
- 依赖倒置:依赖抽象,不依赖实现
【简单】什么是迪米特法则?
迪米特法则(Law of Demeter,LoD)又称最少知识原则,核心思想是:一个对象应当对其他对象有尽可能少的了解,即只与直接的朋友通信,不与陌生人说话。
【简单】什么是合成复用原则?
合成复用原则(Composite Reuse Principle)核心是:优先使用对象组合(has-a),而不是类继承(is-a)来实现复用。
- 为什么优先组合
- 继承破坏封装:子类依赖父类实现细节,父类变更可能影响子类。
- 组合更灵活:可在运行时动态组合对象、改变行为,耦合度低。
- 符合开闭原则:通过组合已有对象扩展新功能,无需修改原有代码。
- 何时用继承:只有当类之间确实存在严格的“is-a”关系,且子类不会改变父类行为时才考虑继承。
记忆点:组合优于继承,耦合低更灵活,继承只用于真正的 is-a。
创建型模式
【中等】什么是单例模式?⭐⭐⭐
单例模式 (Singleton) 确保全局只有唯一实例。
代码结构

单例 (Singleton) 类声明了一个名为 getInstance 获取实例的静态方法来返回其所属类的一个相同实例。
单例的构造函数必须对客户端 (Client) 代码隐藏。 调用 getInstance 方法必须是获取单例对象的唯一方式。
所有单例的实现都包含以下两个相同的步骤:
- 将默认构造函数设为私有, 防止其他对象使用单例类的
new运算符。 - 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象。
应用场景
- 配置管理类:系统配置信息全局共享,只需加载一次,避免重复读取配置。
- 日志记录器:避免重复创建文件句柄或网络连接,保证日志写入的一致性和性能。
- 线程池:全局管理线程资源,避免频繁创建销毁线程,提升系统效率。
- Spring Bean 默认作用域:IoC 容器中多数 Bean 设计为单例,减少对象创建开销,方便依赖注入。
- 缓存管理器:全局缓存数据,避免多个缓存实例造成数据不一致和内存浪费。
- 运行时环境类:如 Java 的
Runtime类,代表应用程序的运行环境,天然单例。
【困难】单例模式有哪几种实现?如何保证线程安全?⭐⭐⭐
饿汉式(线程安全,类加载时初始化)
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {}
public static Singleton getInstance() {
return instance;
}
}懒汉式(线程不安全,需改进)
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}双重检查锁定(线程安全,推荐)
public class Singleton {
private static volatile Singleton instance;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}静态内部类(线程安全,推荐)
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}枚举单例
public enum Singleton {
INSTANCE;
public void bizMethod() {
// 一些业务逻辑方法
}
}
// 使用
Singleton singleton = Singleton.INSTANCE;
singleton.bizMethod();【中等】什么是简单工厂模式?⭐
简单工厂模式 (Simple Factory) 通过一个工厂类根据参数创建不同产品对象,将创建逻辑集中封装,客户端无需了解具体实现。
代码骨架

简单工厂模式通常是定义一个工厂类,这个类可以根据不同变量返回不同类的产品实例。
简单工厂模式是一种对象创建型模式。但是简单工厂模式不属于 23 种 Gof 设计模式之一。
【中等】什么是工厂方法模式?⭐⭐⭐
工厂方法模式 (Factory Method) 由子类决定创建哪个对象。
工厂方法模式在父类中提供一个创建对象的方法, 让子类决定实例化对象的类型。
- 工厂模式中,增加一种产品类,就要增加一个工厂类:因为每个工厂类只能创建一种产品的实例。
- 工厂模式遵循“开放-封闭原则”:工厂模式中,新增一种产品并不需要修改原有类,仅仅是扩展。
代码结构

- 产品 (Product) 将会对接口进行声明。 对于所有由创建者及其子类构建的对象, 这些接口都是通用的。
- 具体产品 (Concrete Products) 是产品接口的不同实现。
- 创建者 (Creator) 类声明返回产品对象的工厂方法。 该方法的返回对象类型必须与产品接口相匹配。
- 你可以将工厂方法声明为抽象方法, 强制要求每个子类以不同方式实现该方法。 或者, 你也可以在基础工厂方法中返回默认产品类型。
- 注意, 尽管它的名字是创建者, 但他最主要的职责并不是创建产品。 一般来说, 创建者类包含一些与产品相关的核心业务逻辑。 工厂方法将这些逻辑处理从具体产品类中分离出来。 打个比方, 大型软件开发公司拥有程序员培训部门。 但是, 这些公司的主要工作还是编写代码, 而非生产程序员。
- 具体创建者 (Concrete Creators) 将会重写基础工厂方法, 使其返回不同类型的产品。
注意, 并不一定每次调用工厂方法都会创建新的实例。 工厂方法也可以返回缓存、 对象池或其他来源的已有对象。
应用场景
- 日志框架:SLF4J 通过
LoggerFactory获取日志记录器,具体实现(Logback、Log4j)由工厂方法动态决定,客户端无需感知底层。 - 数据库连接池:如 HikariCP、Druid 通过
DataSource工厂创建和管理数据库连接,隐藏连接创建、池化、销毁等复杂逻辑。 - Java 集合框架:
Collection.iterator()是一个工厂方法,返回具体迭代器(如ArrayList.Itr),客户端统一遍历集合,不关心内部结构。 - Spring IoC 容器:
BeanFactory和ApplicationContext根据配置(XML、注解)创建和管理 bean 实例,是工厂模式的典型实现。 - JDBC API:
DriverManager.getConnection()根据 URL 返回不同数据库的 Connection 对象,类似简单工厂,屏蔽驱动差异。
【中等】什么是抽象工厂模式?⭐⭐
抽象工厂模式 (Abstract Factory) 用于创建相关或依赖的产品族。
代码骨架

- 抽象产品 (Abstract Product) 为构成系列产品的一组不同但相关的产品声明接口。
- 具体产品 (Concrete Product) 是抽象产品的多种不同类型实现。 所有变体 (维多利亚/现代) 都必须实现相应的抽象产品 (椅子/沙发)。
- 抽象工厂 (Abstract Factory) 接口声明了一组创建各种抽象产品的方法。
- 具体工厂 (Concrete Factory) 实现抽象工厂的构建方法。 每个具体工厂都对应特定产品变体, 且仅创建此种产品变体。
- 尽管具体工厂会对具体产品进行初始化, 其构建方法签名必须返回相应的抽象产品。 这样, 使用工厂类的客户端代码就不会与工厂创建的特定产品变体耦合。 客户端 (Client) 只需通过抽象接口调用工厂和产品对象, 就能与任何具体工厂/产品变体交互。
应用场景
- 多数据库支持(DAO 层):为 MySQL、Oracle 等数据库分别实现用户、订单等 DAO 对象,切换数据库时只需更换工厂,保证各操作类兼容。
- 框架集成:Spring 的
BeanFactory可视为抽象工厂变体,按环境(开发/生产)创建配置相关的 Bean 组。
【中等】工厂模式和抽象工厂模式有什么区别?⭐⭐
- 工厂方法:一个工厂生产一个产品,通过继承创建。
- 抽象工厂:一个工厂生产一簇产品,通过组合创建,强调产品之间的配套约束。
【中等】什么是建造者模式?⭐⭐
建造者模式 (Builder) 用于分步构建复杂对象。
代码骨架

- 建造者 (Builder) 接口声明在所有类型建造者中通用的产品构造步骤。
- 具体建造者 (Concrete Builders) 提供构造过程的不同实现。 具体建造者也可以构造不遵循通用接口的产品。
- 产品 (Products) 是最终生成的对象。 由不同建造者构造的产品无需属于同一类层次结构或接口。
- 主管 (Director) 类定义调用构造步骤的顺序, 这样你就可以创建和复用特定的产品配置。
- 客户端 (Client) 必须将某个建造者对象与主管类关联。 一般情况下, 你只需通过主管类构造函数的参数进行一次性关联即可。 此后主管类就能使用建造者对象完成后续所有的构造任务。 但在客户端将建造者对象传递给主管类制造方法时还有另一种方式。 在这种情况下, 你在使用主管类生产产品时每次都可以使用不同的建造者。
应用场景
- Lombok @Builder
- StringBuilder
- 快餐套餐组合
- HttpClient 配置构建
java.lang.StringBuilder#append()(非同步)java.lang.StringBuffer#append()(同步)java.nio.ByteBuffer#put()(还有CharBuffer、ShortBuffer、IntBuffer、LongBuffer、FloatBuffer和DoubleBuffer)java.lang.Appendable的所有实现
与工厂模式区别
- 工厂:一次性创建,侧重产品类型
- 建造者:分步创建,侧重产品内部结构
【中等】什么是原型模式?
原型模式 (Prototype) 用于克隆生成对象。
原型模式主要用于对象的复制,它的核心是就是类图中的原型类 Prototype。Prototype 类需要具备以下两个条件:
- 实现 Cloneable 接口。在 java 语言有一个 Cloneable 接口,它的作用只有一个,就是在运行时通知虚拟机可以安全地在实现了此接口的类上使用 clone 方法。在 java 虚拟机中,只有实现了这个接口的类才可以被拷贝,否则在运行时会抛出 CloneNotSupportedException 异常。
- 重写 Object 类中的 clone 方法。Java 中,所有类的父类都是 Object 类,Object 类中有一个 clone 方法,作用是返回对象的一个拷贝,但是其作用域 protected 类型的,一般的类无法调用,因此,Prototype 类需要将 clone 方法的作用域修改为 public 类型。
代码骨架

- 原型 (Prototype) 接口将对克隆方法进行声明。 在绝大多数情况下, 其中只会有一个名为
clone克隆的方法。 - 具体原型 (Concrete Prototype) 类将实现克隆方法。 除了将原始对象的数据复制到克隆体中之外, 该方法有时还需处理克隆过程中的极端情况, 例如克隆关联对象和梳理递归依赖等等。
- 客户端 (Client) 可以复制实现了原型接口的任何对象。
应用场景
使用示例: Java 的 Cloneable (可克隆) 接口就是立即可用的原型模式。
任何类都可通过实现该接口来实现可被克隆的性质。
java.lang.Object#clone()(类必须实现java.lang.Cloneable接口)
识别方法: 原型可以简单地通过 clone或 copy等方法来识别。
结构型模式
【中等】什么是适配器模式?⭐⭐
适配器模式 (Adapter) 用于接口转换,兼容不匹配类。
适配器模式通过封装对象将复杂的转换过程隐藏于幕后。 被封装的对象甚至察觉不到适配器的存在。
代码骨架
适配器实现了其中一个对象的接口, 并对另一个对象进行封装。

- 客户端 (Client) 是包含当前程序业务逻辑的类。
- 客户端接口 (Client Interface) 描述了其他类与客户端代码合作时必须遵循的协议。
- 服务 (Service) 中有一些功能类 (通常来自第三方或遗留系统)。 客户端与其接口不兼容, 因此无法直接调用其功能。
- 适配器 (Adapter) 是一个可以同时与客户端和服务交互的类: 它在实现客户端接口的同时封装了服务对象。 适配器接受客户端通过适配器接口发起的调用, 并将其转换为适用于被封装服务对象的调用。
- 客户端代码只需通过接口与适配器交互即可, 无需与具体的适配器类耦合。 因此, 你可以向程序中添加新类型的适配器而无需修改已有代码。 这在服务类的接口被更改或替换时很有用: 你无需修改客户端代码就可以创建新的适配器类。
应用场景
java.util.Arrays#asList()java.util.Collections#list()java.util.Collections#enumeration()java.io.InputStreamReader(InputStream)(返回Reader对象)java.io.OutputStreamWriter(OutputStream)(返回Writer对象)
【中等】什么是桥接模式?
桥接模式 (Bridge) 用于抽象与实现分离,独立变化。
代码骨架

- 抽象部分 (Abstraction) 提供高层控制逻辑, 依赖于完成底层实际工作的实现对象。
- 实现部分 (Implementation) 为所有具体实现声明通用接口。 抽象部分仅能通过在这里声明的方法与实现对象交互。
- 抽象部分可以列出和实现部分一样的方法, 但是抽象部分通常声明一些复杂行为, 这些行为依赖于多种由实现部分声明的原语操作。
- 具体实现 (Concrete Implementations) 中包括特定于平台的代码。
- 精确抽象 (Refined Abstraction) 提供控制逻辑的变体。 与其父类一样, 它们通过通用实现接口与不同的实现进行交互。
- 通常情况下, 客户端 (Client) 仅关心如何与抽象部分合作。 但是, 客户端需要将抽象对象与一个实现对象连接起来。
应用场景
桥接模式在处理跨平台应用、 支持多种类型的数据库服务器或与多个特定种类 (例如云平台和社交网络等) 的 API 供应商协作时会特别有用。
桥接可以通过一些控制实体及其所依赖的多个不同平台之间的明确区别来进行识别。
Java 中桥接模式应用最经典的代表无疑是日志组件 slf4j 的桥接 jar 包。
假如,你正在开发应用程序所调用的组件当中已经使用了 common-logging,这时你需要 jcl-over-slf4j.jar 把日志信息输出重定向到 slf4j-api,slf4j-api 再去调用 slf4j 实际依赖的日志组件。这个过程称为桥接。下图是官方的 slf4j 桥接策略图:

【中等】什么是组合模式?
组合模式 (Composite) 用于树形结构表示整体-部分。
代码骨架

- 组件 (Component) 接口描述了树中简单项目和复杂项目所共有的操作。
- 叶节点 (Leaf) 是树的基本结构, 它不包含子项目。一般情况下, 叶节点最终会完成大部分的实际工作, 因为它们无法将工作指派给其他部分。
- 容器 (Container)——又名 “组合 (Composite)”——是包含叶节点或其他容器等子项目的单位。 容器不知道其子项目所属的具体类, 它只通过通用的组件接口与其子项目交互。容器接收到请求后会将工作分配给自己的子项目, 处理中间结果, 然后将最终结果返回给客户端。
- 客户端 (Client) 通过组件接口与所有项目交互。 因此, 客户端能以相同方式与树状结构中的简单或复杂项目交互。
应用场景
- 文件系统:目录(文件夹)包含文件或子目录,统一提供获取大小、删除等操作,客户端无差别对待单个文件或整个目录。
- 缓存组合:如 Spring Cache 的
CompositeCacheManager,组合多个缓存管理器,统一处理缓存操作。
【中等】什么是装饰器模式?⭐⭐
装饰模式 (Decorator) 用于动态添加职责。
代码骨架

- 部件 (Component) 声明封装器和被封装对象的公用接口。
- 具体部件 (Concrete Component) 类是被封装对象所属的类。 它定义了基础行为, 但装饰类可以改变这些行为。
- 基础装饰 (Base Decorator) 类拥有一个指向被封装对象的引用成员变量。 该变量的类型应当被声明为通用部件接口, 这样它就可以引用具体的部件和装饰。 装饰基类会将所有操作委派给被封装的对象。
- 具体装饰类 (Concrete Decorators) 定义了可动态添加到部件的额外行为。 具体装饰类会重写装饰基类的方法, 并在调用父类方法之前或之后进行额外的行为。
- 客户端 (Client) 可以使用多层装饰来封装部件, 只要它能使用通用接口与所有对象互动即可。
应用场景
java.io.InputStream、OutputStream、Reader和Writer的所有代码都有以自身类型的对象作为参数的构造函数。java.util.Collections;checkedXXX()、synchronizedXXX()和unmodifiableXXX()方法。javax.servlet.http.HttpServletRequestWrapper和HttpServletResponseWrapper
【中等】什么是外观模式?
外观模式 (Facade) 为子系统提供统一接口。
代码骨架

外观 (Facade) 提供了一种访问特定子系统功能的便捷方式, 其了解如何重定向客户端请求, 知晓如何操作一切活动部件。
创建附加外观 (Additional Facade) 类可以避免多种不相关的功能污染单一外观, 使其变成又一个复杂结构。 客户端和其他外观都可使用附加外观。
复杂子系统 (Complex Subsystem) 由数十个不同对象构成。 如果要用这些对象完成有意义的工作, 你必须深入了解子系统的实现细节, 比如按照正确顺序初始化对象和为其提供正确格式的数据。
子系统类不会意识到外观的存在, 它们在系统内运作并且相互之间可直接进行交互。
客户端 (Client) 使用外观代替对子系统对象的直接调用。
应用场景
- JDBC 驱动管理:
DriverManager屏蔽不同数据库驱动的加载和连接创建细节,客户端通过统一接口获取连接。 - Spring JdbcTemplate:封装连接获取、语句执行、结果集处理、异常转换,提供简洁的数据库操作入口。
- SLF4J 日志门面:为 Logback、Log4j 等日志框架提供统一 API,客户端面向门面编程,底层可随时切换。
【中等】什么是享元模式?
享元模式 (Flyweight) 共享细粒度对象,节省内存。
代码骨架

- 享元模式只是一种优化。 在应用该模式之前, 你要确定程序中存在与大量类似对象同时占用内存相关的内存消耗问题, 并且确保该问题无法使用其他更好的方式来解决。
- 享元 (Flyweight) 类包含原始对象中部分能在多个对象中共享的状态。 同一享元对象可在许多不同情景中使用。 享元中存储的状态被称为 “内在状态”。 传递给享元方法的状态被称为 “外在状态”。
- 情景 (Context) 类包含原始对象中各不相同的外在状态。 情景与享元对象组合在一起就能表示原始对象的全部状态。
- 通常情况下, 原始对象的行为会保留在享元类中。 因此调用享元方法必须提供部分外在状态作为参数。 但你也可将行为移动到情景类中, 然后将连入的享元作为单纯的数据对象。
- 客户端 (Client) 负责计算或存储享元的外在状态。 在客户端看来, 享元是一种可在运行时进行配置的模板对象, 具体的配置方式为向其方法中传入一些情景数据参数。
- 享元工厂 (Flyweight Factory) 会对已有享元的缓存池进行管理。 有了工厂后, 客户端就无需直接创建享元, 它们只需调用工厂并向其传递目标享元的一些内在状态即可。 工厂会根据参数在之前已创建的享元中进行查找, 如果找到满足条件的享元就将其返回; 如果没有找到就根据参数新建享元。
应用场景
使用示例: 享元模式只有一个目的: 将内存消耗最小化。 如果你的程序没有遇到内存容量不足的问题, 则可以暂时忽略该模式。
享元模式在核心 Java 程序库中的示例:
识别方法: 享元可以通过构建方法来识别, 它会返回缓存对象而不是创建新的对象。
【中等】什么是代理模式?⭐⭐⭐
代理模式 (Proxy) 用于控制对象访问。
代码骨架

- 服务接口 (Service Interface) 声明了服务接口。 代理必须遵循该接口才能伪装成服务对象。
- 服务 (Service) 类提供了一些实用的业务逻辑。
- 代理 (Proxy) 类包含一个指向服务对象的引用成员变量。 代理完成其任务 (例如延迟初始化、 记录日志、 访问控制和缓存等) 后会将请求传递给服务对象。 通常情况下, 代理会对其服务对象的整个生命周期进行管理。
- 客户端 (Client) 能通过同一接口与服务或代理进行交互, 所以你可在一切需要服务对象的代码中使用代理。
应用场景
- 远程代理:隐藏对象位于不同地址空间的事实,如 RPC 框架的客户端 Stub、Feign 动态代理。
- 虚拟代理:延迟创建开销大的对象,直到真正需要时(如 Hibernate 懒加载、大图片占位)。
- 保护代理:控制访问权限,校验调用者身份(如 Spring 方法级安全注解)。
- 智能引用:在访问时附加额外操作,如访问计数、日志记录、性能监控。
- 缓存代理:缓存方法结果,减少重复计算(如 Spring @Cacheable)。
- 防火墙代理:保护目标免受恶意访问,控制网络资源。
实现方式
- 静态代理:手动编写代理类,编译前确定。
- 动态代理:运行时生成代理,如 JDK 动态代理(基于接口)和 CGLIB(基于子类)。
【中等】装饰器、适配器、代理、桥接这四种设计模式有什么区别?⭐⭐
| 模式 | 核心意图 | 结构特点 | 典型场景 |
|---|---|---|---|
| 装饰器 | 动态增强对象功能 | 包装真实对象,接口一致,递归组合 | IO 流、Spring 事务注解 |
| 适配器 | 接口转换,让不兼容的类协同工作 | 包装被适配者,实现目标接口 | 日志门面 SLF4J、第三方库适配 |
| 代理 | 控制对象访问,延迟加载或权限控制 | 代理与目标实现同一接口,代理持有引用 | RPC 客户端、AOP 代理、懒加载 |
| 桥接 | 分离抽象与实现,独立变化 | 抽象持有实现接口的引用,二者可各自扩展 | 跨平台 UI 组件、JDBC 驱动 |
行为型模式
【中等】什么是模板方法模式?⭐⭐⭐
模板方法模式 (Template Method) 设置算法骨架,子类实现步骤。
代码骨架

- 抽象类 (AbstractClass) 会声明作为算法步骤的方法, 以及依次调用它们的实际模板方法。 算法步骤可以被声明为
抽象类型, 也可以提供一些默认实现。 - 具体类 (ConcreteClass) 可以重写所有步骤, 但不能重写模板方法自身。
应用场景
使用示例: 模版方法模式在 Java 框架中很常见。 开发者通常使用它来向框架用户提供通过继承实现的、 对标准功能进行扩展的简单方式。
这里是一些核心 Java 程序库中模版方法的示例:
java.io.InputStream、java.io.OutputStream、java.io.Reader和java.io.Writer的所有非抽象方法。java.util.AbstractList、java.util.AbstractSet和java.util.AbstractMap的所有非抽象方法。javax.servlet.http.HttpServlet, 所有默认发送 HTTP 405 “方法不允许” 错误响应的doXXX()方法。 你可随时对其进行重写。
识别方法: 模版方法可以通过行为方法来识别, 该方法已有一个在基类中定义的 “默认” 行为。
【中等】什么是策略模式?⭐⭐⭐
策略模式 (Strategy) 使得算法可替换。
代码骨架

- 上下文 (Context) 维护指向具体策略的引用, 且仅通过策略接口与该对象进行交流。
- 策略 (Strategy) 接口是所有具体策略的通用接口, 它声明了一个上下文用于执行策略的方法。
- 具体策略 (Concrete Strategies) 实现了上下文所用算法的各种不同变体。
- 当上下文需要运行算法时, 它会在其已连接的策略对象上调用执行方法。 上下文不清楚其所涉及的策略类型与算法的执行方式。
- 客户端 (Client) 会创建一个特定策略对象并将其传递给上下文。 上下文则会提供一个设置器以便客户端在运行时替换相关联的策略。
应用场景
- 对
java.util.Comparator#compare()的调用来自Collections#sort(). javax.servlet.http.HttpServlet:service()方法, 还有所有接受HttpServletRequest和HttpServletResponse对象作为参数的doXXX()方法。javax.servlet.Filter#doFilter()- Dubbo 中负载均衡算法采用了策略模式,便于切换算法。
【中等】什么是观察者模式?⭐⭐⭐
观察者模式 (Observer) 用于一对多通知依赖者。
代码骨架

- 发布者 (Publisher) 会向其他对象发送值得关注的事件。 事件会在发布者自身状态改变或执行特定行为后发生。 发布者中包含一个允许新订阅者加入和当前订阅者离开列表的订阅构架。
- 当新事件发生时, 发送者会遍历订阅列表并调用每个订阅者对象的通知方法。 该方法是在订阅者接口中声明的。
- 订阅者 (Subscriber) 接口声明了通知接口。 在绝大多数情况下, 该接口仅包含一个
update更新方法。 该方法可以拥有多个参数, 使发布者能在更新时传递事件的详细信息。 - 具体订阅者 (Concrete Subscribers) 可以执行一些操作来回应发布者的通知。 所有具体订阅者类都实现了同样的接口, 因此发布者不需要与具体类相耦合。
- 订阅者通常需要一些上下文信息来正确地处理更新。 因此, 发布者通常会将一些上下文数据作为通知方法的参数进行传递。 发布者也可将自身作为参数进行传递, 使订阅者直接获取所需的数据。
- 客户端 (Client) 会分别创建发布者和订阅者对象, 然后为订阅者注册发布者更新。
应用场景
java.util.Observer/java.util.Observable(极少在真实世界中使用)java.util.EventListener的所有实现 (几乎广泛存在于 Swing 组件中)javax.servlet.http.HttpSessionBindingListenerjavax.servlet.http.HttpSessionAttributeListenerjavax.faces.event.PhaseListener
【中等】什么是状态模式?⭐⭐
状态模式 (State):状态改变行为。
代码骨架

- 上下文 (Context) 保存了对于一个具体状态对象的引用, 并会将所有与该状态相关的工作委派给它。 上下文通过状态接口与状态对象交互, 且会提供一个设置器用于传递新的状态对象。
- 状态 (State) 接口会声明特定于状态的方法。 这些方法应能被其他所有具体状态所理解, 因为你不希望某些状态所拥有的方法永远不会被调用。
- 具体状态 (Concrete States) 会自行实现特定于状态的方法。 为了避免多个状态中包含相似代码, 你可以提供一个封装有部分通用行为的中间抽象类。
- 状态对象可存储对于上下文对象的反向引用。 状态可以通过该引用从上下文处获取所需信息, 并且能触发状态转移。
- 上下文和具体状态都可以设置上下文的下个状态, 并可通过替换连接到上下文的状态对象来完成实际的状态转换。
应用场景
- Spring 状态机
- 将订单、流程等状态流转抽象为状态机模型,通过配置状态、事件、转换来管理复杂的业务状态。
- 核心要素包括当前状态、触发事件、响应函数、目标状态。
- Netty 网络框架
- 在通道处理器(
ChannelHandler)中存储连接状态(如登录态、协议版本),根据状态决定消息处理逻辑。 - 实现有状态协议(如同步请求-响应)时,通过状态变量控制消息发送时机,避免并发冲突。
- 在通道处理器(
【中等】什么是职责链模式?⭐⭐
职责链模式 (Chain of Responsibility):请求沿链传递,多处理器
代码骨架

处理者 (Handler) 声明了所有具体处理者的通用接口。 该接口通常仅包含单个方法用于请求处理, 但有时其还会包含一个设置链上下个处理者的方法。
基础处理者 (Base Handler) 是一个可选的类, 你可以将所有处理者共用的样本代码放置在其中。
通常情况下, 该类中定义了一个保存对于下个处理者引用的成员变量。 客户端可通过将处理者传递给上个处理者的构造函数或设定方法来创建链。 该类还可以实现默认的处理行为: 确定下个处理者存在后再将请求传递给它。
具体处理者 (Concrete Handlers) 包含处理请求的实际代码。 每个处理者接收到请求后, 都必须决定是否进行处理, 以及是否沿着链传递请求。
处理者通常是独立且不可变的, 需要通过构造函数一次性地获得所有必要地数据。
客户端 (Client) 可根据程序逻辑一次性或者动态地生成链。 值得注意的是, 请求可发送给链上的任意一个处理者, 而非必须是第一个处理者。
应用场景
- Servlet Filter(过滤器链)
- 每个过滤器对请求或响应进行处理,决定是否继续调用下一个过滤器或目标 Servlet。
- 典型应用:日志记录、权限校验、字符编码设置。
- Spring Interceptor(拦截器链)
- 在 Spring MVC 中,拦截器按配置顺序执行
preHandle、postHandle、afterCompletion。 - 任一拦截器返回 false 即可中断请求,常用于登录检查、性能监控。
- 在 Spring MVC 中,拦截器按配置顺序执行
- Netty 的 ChannelPipeline
- 每个 ChannelHandler 处理入站或出站事件,可选择传递给下一个处理器。
- 实现协议编解码、流量整形、业务逻辑的灵活编排。
- Spring Security 过滤器链
- 由多个
SecurityFilter组成(如UsernamePasswordAuthenticationFilter、ExceptionTranslationFilter),依次处理认证和授权。 - 可动态配置过滤器顺序和组合。
- 由多个
- MyBatis 插件(Interceptor)
- 插件实现
Interceptor接口,通过责任链拦截Executor、StatementHandler等核心对象的方法调用。 - 用于分页、性能监控、SQL 重写等扩展。
- 插件实现
【中等】什么是命令模式?
命令模式 (Command):请求封装为对象,支持操作队列
代码骨架

发送者 (Sender)——亦称 “触发者 (Invoker)”——类负责对请求进行初始化, 其中必须包含一个成员变量来存储对于命令对象的引用。 发送者触发命令, 而不向接收者直接发送请求。 注意, 发送者并不负责创建命令对象: 它通常会通过构造函数从客户端处获得预先生成的命令。
命令 (Command) 接口通常仅声明一个执行命令的方法。
具体命令 (Concrete Commands) 会实现各种类型的请求。 具体命令自身并不完成工作, 而是会将调用委派给一个业务逻辑对象。 但为了简化代码, 这些类可以进行合并。
接收对象执行方法所需的参数可以声明为具体命令的成员变量。 你可以将命令对象设为不可变, 仅允许通过构造函数对这些成员变量进行初始化。
接收者 (Receiver) 类包含部分业务逻辑。 几乎任何对象都可以作为接收者。 绝大部分命令只处理如何将请求传递到接收者的细节, 接收者自己会完成实际的工作。
客户端 (Client) 会创建并配置具体命令对象。 客户端必须将包括接收者实体在内的所有请求参数传递给命令的构造函数。 此后, 生成的命令就可以与一个或多个发送者相关联了。
应用场景
- JDK 并发框架
Runnable和Callable将线程执行的命令封装为对象,提交给Executor或Thread执行,实现任务创建与执行分离。ThreadPoolExecutor内部将任务对象放入阻塞队列,工作线程从队列中取出并执行。
- Spring 框架
JdbcTemplate将数据库操作封装为PreparedStatementCallback或ResultSetExtractor等命令对象,统一管理连接和事务。PlatformTransactionManager将事务的提交、回滚等操作封装为命令,支持编程式和声明式事务。
- Netty 网络框架
ChannelFuture和ChannelPromise将异步 I/O 操作封装为命令,通过回调通知结果。- 编解码器将消息的读取和写入封装为命令,在
ChannelPipeline中传递。
- 消息中间件(RabbitMQ / Kafka 客户端)
- 生产者将消息封装为命令对象(如
RabbitTemplate的send()方法内部构建Message对象),通过网络发送。 - 消费者将接收到的消息封装为命令对象,传递给业务处理器。
- 生产者将消息封装为命令对象(如
【中等】什么是迭代器模式?
迭代器模式 (Iterator):顺序访问聚合元素
代码骨架

- 迭代器 (Iterator) 接口声明了遍历集合所需的操作: 获取下一个元素、 获取当前位置和重新开始迭代等。
- 具体迭代器 (Concrete Iterators) 实现遍历集合的一种特定算法。 迭代器对象必须跟踪自身遍历的进度。 这使得多个迭代器可以相互独立地遍历同一集合。
- 集合 (Collection) 接口声明一个或多个方法来获取与集合兼容的迭代器。 请注意, 返回方法的类型必须被声明为迭代器接口, 因此具体集合可以返回各种不同种类的迭代器。
- 具体集合 (Concrete Collections) 会在客户端请求迭代器时返回一个特定的具体迭代器类实体。 你可能会琢磨, 剩下的集合代码在什么地方呢? 不用担心, 它也会在同一个类中。 只是这些细节对于实际模式来说并不重要, 所以我们将其省略了而已。
- 客户端 (Client) 通过集合和迭代器的接口与两者进行交互。 这样一来客户端无需与具体类进行耦合, 允许同一客户端代码使用各种不同的集合和迭代器。
- 客户端通常不会自行创建迭代器, 而是会从集合中获取。 但在特定情况下, 客户端可以直接创建一个迭代器 (例如当客户端需要自定义特殊迭代器时)。
应用场景
java.util.Iterator的所有实现 (还有java.util.Scanner)。java.util.Enumeration的所有实现
【中等】什么是中介者模式?
中介者模式 (Mediator):封装对象间交互,降低耦合
代码骨架

- 组件 (Component) 是各种包含业务逻辑的类。 每个组件都有一个指向中介者的引用, 该引用被声明为中介者接口类型。 组件不知道中介者实际所属的类, 因此你可通过将其连接到不同的中介者以使其能在其他程序中复用。
- 中介者 (Mediator) 接口声明了与组件交流的方法, 但通常仅包括一个通知方法。 组件可将任意上下文 (包括自己的对象) 作为该方法的参数, 只有这样接收组件和发送者类之间才不会耦合。
- 具体中介者 (Concrete Mediator) 封装了多种组件间的关系。 具体中介者通常会保存所有组件的引用并对其进行管理, 甚至有时会对其生命周期进行管理。
- 组件并不知道其他组件的情况。 如果组件内发生了重要事件, 它只能通知中介者。 中介者收到通知后能轻易地确定发送者, 这或许已足以判断接下来需要触发的组件了。
- 对于组件来说, 中介者看上去完全就是一个黑箱。 发送者不知道最终会由谁来处理自己的请求, 接收者也不知道最初是谁发出了请求。
应用场景
java.util.Timer(所有scheduleXXX()方法)java.util.concurrent.Executor#execute()java.util.concurrent.ExecutorService(invokeXXX()和submit()方法)java.util.concurrent.ScheduledExecutorService(所有scheduleXXX()方法)java.lang.reflect.Method#invoke()
【中等】什么是访问者模式?
访问者模式 (Visitor):在不改变元素类前提下增加新操作
代码骨架

- 访问者 (Visitor) 接口声明了一系列以对象结构的具体元素为参数的访问者方法。 如果编程语言支持重载, 这些方法的名称可以是相同的, 但是其参数一定是不同的。
- 具体访问者 (Concrete Visitor) 会为不同的具体元素类实现相同行为的几个不同版本。
- 元素 (Element) 接口声明了一个方法来 “接收” 访问者。 该方法必须有一个参数被声明为访问者接口类型。
- 具体元素 (Concrete Element) 必须实现接收方法。 该方法的目的是根据当前元素类将其调用重定向到相应访问者的方法。 请注意, 即使元素基类实现了该方法, 所有子类都必须对其进行重写并调用访问者对象中的合适方法。
- 客户端 (Client) 通常会作为集合或其他复杂对象 (例如一个 组合 树) 的代表。 客户端通常不知晓所有的具体元素类, 因为它们会通过抽象接口与集合中的对象进行交互。
应用场景
- JDK NIO 的
FileVisitor接口让文件树遍历与具体操作(查找、删除)分离。 - Apache Calcite 的
SqlVisitor在不修改 AST 节点的情况下执行类型检查、优化等操作。 - Apache Commons Lang 的
LockingVisitors将锁管理与对受保护对象的读写操作解耦。 - Lombok 在编译期作为访问者遍历 AST,根据注解动态添加方法或字段。
- Java 编译器 API 的
ElementVisitor在注解处理时遍历源代码元素(类、方法等)并执行自定义逻辑。
【中等】什么是备忘录模式?
备忘录模式 (Memento):保存和恢复对象状态
代码骨架
基于嵌套类的实现

原发器 (Originator) 类可以生成自身状态的快照, 也可以在需要时通过快照恢复自身状态。
备忘录 (Memento) 是原发器状态快照的值对象 (value object)。 通常做法是将备忘录设为不可变的, 并通过构造函数一次性传递数据。
负责人 (Caretaker) 仅知道 “何时” 和 “为何” 捕捉原发器的状态, 以及何时恢复状态。
负责人通过保存备忘录栈来记录原发器的历史状态。 当原发器需要回溯历史状态时, 负责人将从栈中获取最顶部的备忘录, 并将其传递给原发器的恢复 (restoration) 方法。
在该实现方法中, 备忘录类将被嵌套在原发器中。 这样原发器就可访问备忘录的成员变量和方法, 即使这些方法被声明为私有。 另一方面, 负责人对于备忘录的成员变量和方法的访问权限非常有限: 它们只能在栈中保存备忘录, 而不能修改其状态。
基于中间接口的实现
另外一种实现方法适用于不支持嵌套类的编程语言 (没错, 我说的就是 PHP)。

- 在没有嵌套类的情况下, 你可以规定负责人仅可通过明确声明的中间接口与备忘录互动, 该接口仅声明与备忘录元数据相关的方法, 限制其对备忘录成员变量的直接访问权限。
- 另一方面, 原发器可以直接与备忘录对象进行交互, 访问备忘录类中声明的成员变量和方法。 这种方式的缺点在于你需要将备忘录的所有成员变量声明为公有。
封装更加严格的实现
如果你不想让其他类有任何机会通过备忘录来访问原发器的状态, 那么还有另一种可用的实现方式。

- 这种实现方式允许存在多种不同类型的原发器和备忘录。 每种原发器都和其相应的备忘录类进行交互。 原发器和备忘录都不会将其状态暴露给其他类。
- 负责人此时被明确禁止修改存储在备忘录中的状态。 但负责人类将独立于原发器, 因为此时恢复方法被定义在了备忘录类中。
- 每个备忘录将与创建了自身的原发器连接。 原发器会将自己及状态传递给备忘录的构造函数。 由于这些类之间的紧密联系, 只要原发器定义了合适的设置器 (setter), 备忘录就能恢复其状态。
应用场景
- JDK 标准库
java.io.Serializable通过序列化将对象状态保存为字节流,需要时反序列化恢复,是广义的备忘录实现。java.util.Date的clone()方法可创建对象副本,本质上是备忘录的简化形式。
- Spring 框架
- Spring Web Flow 在用户导航过程中保存每个页面的状态,支持回退到之前的状态,体现备忘录的保存与恢复概念。
- 声明式事务管理 事务开始时保存数据库状态,失败时回滚到事务前状态,实现状态恢复机制。
- Bean 状态管理
BeanFactory和ApplicationContext允许定义、保存和恢复 Bean 的配置状态。