设计模式原则与创建型模式与结构型模式

共 62 题
📑 题目列表 62 题
#
★★★

1. @Configuration + @Bean 的工厂方法

请说明 Spring 中 @Configuration + @Bean 作为工厂方法模式的应用?

  • @Configuration 的 Bean 工厂角色
  • @Bean 方法即工厂方法
  • 与工厂模式的关系

@Configuration 类在 Spring 中充当"工厂",其 @Bean 方法定义如何创建并装配 Bean,相当于工厂方法模式——每个 @Bean 方法是一个工厂方法,负责创建特定类型的对象。Spring 通过 CGLIB 代理 Configuration 类,保证 @Bean 方法之间调用返回单例(同一 Bean),避免重复创建。这体现了工厂方法模式"把创建逻辑封装到方法"的思想,同时由容器统一管理生命周期。

Spring 的 @Bean 是"声明式工厂方法",把对象创建与配置集中,容器接管生命周期。CGLIB 代理保证单例语义。

#
★★★

2. Prototype Bean 与原型模式的边界

请说明 Spring Prototype Bean 与原型模式的边界?

  • Prototype Bean 每次获取新实例
  • 原型模式(Prototype)的复制
  • 边界与适用场景

Spring 的 @Scope("prototype") 每次注入/获取都创建新实例,与原型模式(Prototype)目标相似——都是为了"新对象",但实现不同:原型模式通过 clone() 复制已有对象,Spring Prototype 通过工厂每次新建。边界在于:Spring Prototype 是"每次新建",原型模式是"复制原型"。Spring 的 Prototype 适合有状态、不可共享的 Bean;原型模式的 clone 适合复制开销低的场景。跨边界理解:都是"避免共享"的手段,但机制不同。

Spring Prototype 与原型模式在"每次新对象"上一致,但一个是容器新建、一个是克隆复制。适用场景需区分。

#
★★★

3. Spring AOP 作为代理模式的工程实现

请说明 Spring AOP 作为代理模式的工程实现?

  • AOP 的横切关注点
  • JDK 动态代理与 CGLIB
  • 代理模式的封装

Spring AOP 通过代理模式实现横切关注点(事务、日志、安全)的织入:对 Bean 创建代理对象,在方法调用前后插入增强逻辑。默认接口用 JDK 动态代理,无接口用 CGLIB。代理模式让"目标对象"与"增强逻辑"解耦,调用方只面对代理。AOP 的 @Transactional 等就是通过代理拦截方法。理解代理是理解 AOP 生效边界(如自调用不生效)的关键。

AOP 是代理模式的工程化,代理封装目标与增强。自调用绕代理是常见坑,源于代理只拦截外部调用。

#
★★★

4. Spring Framework 7 的 ObjectProvider 在延迟获取与原型 Bean 注入的工程价值

请说明 Spring Framework 7 的 ObjectProvider 在延迟获取与原型 Bean 注入中的工程价值?

  • ObjectProvider 的延迟获取
  • 可选依赖与原型注入
  • 避免循环依赖

ObjectProvider<T> 通过 getObject()/getIfAvailable() 延迟获取 Bean,支持可选依赖、延迟绑定与按需解析。工程价值:解决单例依赖原型 Bean 时每次获取新实例的问题(注入 ObjectProvider 而非直接注入 Bean);实现可选依赖(存在与否不阻塞启动);避免部分循环依赖(延迟到调用时解析)。Spring Framework 7 中 ObjectProvider 仍是延迟与可选依赖的推荐方式。

ObjectProvider 把"获取时机"延迟到调用时,是处理原型注入、可选依赖、循环依赖的优雅手段。

#
★★★

5. Spring 的 BeanFactory 作为工厂模式的实现

请说明 Spring 的 BeanFactory 作为工厂模式的实现?

  • BeanFactory 的工厂职责
  • getBean 的创建/获取
  • 与 ApplicationContext 的关系

BeanFactory 是 Spring IoC 容器的核心接口,职责是"管理 Bean 的创建与获取",通过 getBean() 按名称/类型返回 Bean,是工厂模式(Factory Pattern)的典型实现——调用方不关心对象如何创建,只从容器获取。ApplicationContext 是 BeanFactory 的增强(增加事件、资源、AOP 等)。BeanFactory 封装了对象创建的生命周期(单例/原型、初始化、依赖注入)。

BeanFactory 是"对象工厂",把创建与获取解耦,是工厂模式在容器层面的体现。ApplicationContext 在其上扩展。

#
★★★

6. 依赖倒置原则(DIP)与 Spring IoC

请说明依赖倒置原则(DIP)与 Spring IoC 的关系?

  • DIP 的"依赖抽象"
  • IoC 的反转控制
  • 二者结合

依赖倒置原则(DIP)主张"高层模块不应依赖低层模块,都应依赖抽象"。Spring IoC(控制反转)通过依赖注入让容器把依赖装配进对象,而不是对象自己创建,从而实现了"依赖抽象"——对象只声明接口,具体实现由容器注入。DIP 是设计原则,IoC 是实现机制,二者结合让代码面向接口、可替换、可测试。

DIP 是"依赖抽象"的目标,IoC 是"依赖注入"的机制。Spring 是 DIP 的工程落地。

#
★★★

7. 单例模式(Singleton)在 Spring 容器中的 @Scope("singleton") 与 JDK 25 虚拟线程下的并发安全

请说明单例模式在 Spring 容器中的 @Scope("singleton") 与 JDK 25 虚拟线程下的并发安全?

  • Spring 单例 Bean 的并发安全
  • 无状态与有状态 Bean
  • 虚拟线程下的并发

Spring 的 @Scope("singleton") 保证容器内一个 Bean 单例,但单例 Bean 的并发安全取决于其内部状态:无状态 Bean(只依赖注入的其他 Bean)天然线程安全;有状态 Bean(含可变字段)需自行保证并发安全(同步、不可变、ThreadLocal)。JDK 25 虚拟线程下,虚拟线程数可能非常多,并发访问单例 Bean 的共享状态更常见,需更谨慎处理可变状态,避免数据竞争。单例 Bean 自身的内存可见性由 Java 内存模型保证,但业务状态需同步。

单例是"实例唯一",并发安全是"状态安全"。虚拟线程提升并发度,共享状态竞争更突出。

#
★★★

8. 好莱坞原则(Hollywood Principle)与模板方法模式、IoC 容器的协作

请说明好莱坞原则(Hollywood Principle)与模板方法模式、IoC 容器的协作?

  • 好莱坞原则"不要调用我们,我们会调用你"
  • 模板方法模式的控制反转
  • IoC 容器的协作

好莱坞原则(Don't call us, we'll call you)强调"控制权反转":框架/容器调用用户的代码,而不是用户调用框架。模板方法模式体现该原则——父类定义算法骨架,子类实现钩子方法,由父类调用子类实现;IoC 容器同样体现——容器在适当时机调用 Bean 的初始化/销毁方法。三者协作:模板方法在方法级反转控制,IoC 在对象装配级反转控制,核心都是"谁掌控调用时机"。

好莱坞原则是"控制反转"的哲学表达,模板方法与 IoC 是其具体实现。理解"谁调用谁"是核心。

#
★★★

9. 控制反转(IoC)与依赖倒置原则(DIP)的关系辨析,IoC 是原则而 DIP 是目标

请辨析控制反转(IoC)与依赖倒置原则(DIP)的关系:IoC 是原则、DIP 是目标?

  • IoC 的本质(控制反转)
  • DIP 的本质(依赖抽象)
  • 概念辨析

IoC(控制反转)是一种设计原则,指把"对象创建与控制权"从对象内部反转给外部(容器/框架),是"谁控制"的转移;DIP 是"依赖倒置原则",指高层与低层都依赖抽象,是"依赖方向"的要求。二者是不同维度:IoC 讲"控制权的反转",DIP 讲"依赖的抽象化"。Spring 的 IoC 容器通过依赖注入实现 DIP——控制权反转是手段,依赖抽象是目标。

常被混用的两个概念:IoC 是控制权转移,DIP 是依赖方向。Spring IoC 是实现 DIP 的机制。

#
★★★

10. Adapter 在 HttpMessageConverter 的应用

请说明适配器模式(Adapter)在 HttpMessageConverter 中的应用?

  • HttpMessageConverter 的适配
  • 请求/响应体的序列化适配
  • 与媒体类型

HttpMessageConverter 是适配器模式的体现:Spring MVC 用它把 HTTP 请求/响应体与 Java 对象互相转换,适配不同的媒体类型(JSON、XML、表单)。每个 converter 像适配器,把"HTTP 字节流"适配为"Java 对象",或反之。@RequestBody/@ResponseBody 通过选择合适的 converter 完成转换,支持 MappingJackson2HttpMessageConverter(JSON)等。适配器让上层(Controller)与具体序列化实现解耦。

适配器把"客户端期望的接口"与"现有实现"对接。HttpMessageConverter 适配 HTTP 消息与 Java 对象。

#
★★★

11. Bridge 在 JDBC Driver 的应用

请说明桥接模式(Bridge)在 JDBC Driver 中的应用?

  • Bridge 的抽象与实现分离
  • JDBC 接口与驱动实现
  • 数据库异构的适配

桥接模式把"抽象"与"实现"分离,使二者可独立变化。JDBC 是典型应用:java.sql.ConnectionStatement 等抽象接口与具体数据库驱动(MySQL、Oracle)实现分离,应用通过 JDBC 接口编码,运行时由 DriverManager 加载具体驱动,实现"抽象(SQL 标准接口)与实现(各数据库)"解耦。桥接让换数据库无需改应用代码。

JDBC 让"标准接口"与"具体实现"桥接,是桥接模式的经典案例,实现数据库异构兼容。

#
★★★

12. Builder Pattern 在 Lombok @SuperBuilder 的演进

请说明建造者模式(Builder Pattern)在 Lombok @SuperBuilder 中的演进?

  • Builder 模式的可读性
  • @SuperBuilder 的继承支持
  • 相比 @Builder 的演进

Lombok @Builder 生成建造者模式代码,支持链式构建对象,提升可读性。@SuperBuilder 是演进:支持继承场景,让子类与父类的构建属性合并到同一个 builder,SuperBuilder 通过生成父类 builder 与子类 builder 的继承链,解决父类字段无法被子类 builder 覆盖的问题。相比普通 @Builder(不支持继承),@SuperBuilder 适合领域模型有继承的场景。

@SuperBuilder 解决"继承下 builder 的复用",是 Builder 模式在继承层级上的演进。

#
★★

13. Builder Pattern 在 Spring Data QBE(Query By Example)

请说明 Builder Pattern 在 Spring Data QBE(Query By Example)中的应用?

  • QBE 的 Example 构建
  • ExampleMatcher 的构建器
  • 查询条件的组装

Spring Data QBE(Query By Example)用 Example.of(probe, matcher) 构建查询示例,ExampleMatcher 用 Builder 模式(ExampleMatcher.matching() 链式设置匹配规则,如忽略空值、忽略属性、模糊匹配)配置查询条件。Builder 模式让 ExampleMatcher 的复杂配置可读、可组合。QBE 把"按示例查询"的模板与匹配规则解耦,适合动态查询。

ExampleMatcher 用 Builder 链式配置匹配规则,是 Builder 模式在查询条件组装上的应用。

#
★★

14. Collections.unmodifiableList 作为装饰器的应用

请说明 Collections.unmodifiableList 作为装饰器模式的应用?

  • 装饰器模式包装类
  • unmodifiableList 的只读包装
  • 装饰器与代理的区别

Collections.unmodifiableList 包装一个 List,在修改方法(add/remove)上抛异常,实现只读视图,是装饰器模式的典型应用——在原有对象上动态添加"只读"能力而不改变接口。它不复制数据,而是包装原对象,修改操作被拦截。装饰器与代理的区别:装饰器增强功能,代理控制访问。unmodifiableList 是"增强为只读"的装饰。

装饰器在对象上叠加功能,unmodifiableList 叠加"只读保护"。它包装原对象,修改被拒绝。

#
★★

15. Composite 在 java.awt.Container 的应用

请说明组合模式(Composite)在 java.awt.Container 中的应用?

  • Composite 的树形结构
  • Container/Component 的层次
  • 一致处理叶子与组合

java.awt 的 ContainerComponent 体现组合模式:Container 是组合节点,可包含多个 Component(叶子或子 Container),形成树形结构。调用方对单个 Component 与整个 Container 用相同的接口(如 addrepaint),体现"叶子与组合一致处理"。组合模式让 UI 组件的嵌套与整体操作统一。

Composite 让"部分与整体"用一致接口,Container 的嵌套层次是树形结构,统一处理。

#
★★

16. Effective Java 推荐的首选单例实现(枚举 Enum 单例)

请说明 Effective Java 推荐用枚举(Enum)实现单例的理由?

  • 枚举单例的线程安全
  • 防反射、防序列化破坏
  • 与双重检查锁的对比

Effective Java 推荐用枚举实现单例,因为枚举天然保证:线程安全(JVM 保证枚举实例创建一次)、防止反射破坏(反射无法创建枚举实例)、防止序列化破坏(枚举序列化特殊处理,反序列化返回同一实例)。这些是经典单例(双重检查锁)难保证的。唯一缺点是枚举不能继承其他类,某些场景不适用。因此枚举单例是最简洁可靠的单例实现。

枚举单例兼顾线程安全、防反射、防序列化,是"最防破坏"的单例。缺点是限制继承。

#
★★

17. Facade 在 JdbcTemplate 的封装

请说明外观模式(Facade)在 JdbcTemplate 中的封装?

  • Facade 的统一入口
  • JdbcTemplate 封装 JDBC 细节
  • 复杂性屏蔽

JdbcTemplate 是外观模式的典型应用:它封装了 JDBC 的复杂细节(连接管理、Statement 创建、结果集处理、异常转换、资源释放),对外提供统一、简洁的数据库操作接口(query、update、execute)。调用方不再需要处理 JDBC 的样板代码,只需调用 JdbcTemplate 的方法。外观模式让复杂子系统对客户端暴露简单接口。

Facade 把复杂子系统封装为简单接口。JdbcTemplate 屏蔽 JDBC 样板,提升易用性。

#
★★

18. Factory Method 在 Stream.of() 的应用

请说明工厂方法模式(Factory Method)在 Stream.of() 中的应用?

  • Stream.of 的静态工厂方法
  • 工厂方法创建 Stream
  • 静态工厂的用途

Stream.of(...) 是静态工厂方法,根据传入的元素创建并返回 Stream 实例,是工厂方法模式(或静态工厂)的体现——用静态方法封装对象创建,调用方不直接 new。类似的还有 Stream.empty()Collections.emptyList() 等。静态工厂方法的优点:有语义化名称、可返回不同子类、可控制单例。Stream.of 用工厂方法隐藏 Stream 的具体实现类。

静态工厂方法把创建逻辑封装在静态方法中,Stream.of 是典型。语义化命名与实现隐藏是优点。

#
★★

19. 代理模式、装饰器模式与适配器模式在结构上的差异与典型误用辨析

请辨析代理模式、装饰器模式与适配器模式在结构上的差异与典型误用?

  • 三种模式的结构
  • 意图差异(控制/增强/适配)
  • 误用场景

三种模式结构相似(都包装一个对象),但意图不同:代理模式控制访问(对外提供受控接口,如安全、延迟加载);装饰器模式动态增强功能(叠加行为,如缓冲、日志);适配器模式转换接口(解决接口不兼容,把 A 接口适配为 B 接口)。典型误用:把"控制访问"做成"增强功能"(误用代理)、把"接口转换"做成"功能增强"(误用适配器)、把"增强"做成"转换"。辨析关键是看意图:代理管"是否调用",装饰器管"怎么调用",适配器管"接口形态"。

结构相似但意图不同,是三者易误用的根源。根据"控制/增强/转换"的意图选择。

#
★★

20. Filter 在 Spring Security 的应用

请说明过滤器模式(Filter)在 Spring Security 中的应用?

  • Filter 的责任链
  • Spring Security 的过滤器链
  • 认证与授权的过滤

Spring Security 基于 Servlet Filter 链实现:一系列过滤器(认证、授权、CSRF、CORS 等)组成过滤器链,请求依次经过过滤器,每个过滤器可处理或放行。认证过滤器(如 UsernamePasswordAuthenticationFilter)验证身份,授权过滤器(AuthorizationFilter)检查权限。过滤器链是责任链模式的体现,Spring Security 通过过滤器链实现安全控制,且可定制链的顺序与内容。

Spring Security 的过滤器链是责任链/过滤器模式的应用,安全逻辑以过滤器形式织入请求处理。

#
★★

21. Flyweight 在 Integer.valueOf 的应用

请说明享元模式(Flyweight)在 Integer.valueOf 中的应用?

  • 整数缓存(-128~127)
  • 享元的对象复用
  • 与 new 的区别

Integer.valueOf 是享元模式的体现:对 -128~127 范围内的整数,valueOf 返回缓存的共享实例(IntegerCache),避免重复创建,节省内存;超出范围才新建对象。而 new Integer(n) 总是创建新对象。享元模式通过共享重用对象,适合大量重复对象。Integer 的缓存是 Java 内置的享元应用,BooleanCharacter 等也有类似缓存。

享元核心是"共享复用对象"。Integer 缓存让小范围整数共享,是 Java 标准库的享元实现。

#
★★

22. Java I/O 的 InputStream 装饰器层次

请说明 Java I/O 的 InputStream 装饰器层次?

  • InputStream 的装饰器结构
  • BufferedInputStream 等增强
  • 组合使用

Java I/O 的 InputStream 大量使用装饰器模式:BufferedInputStreamDataInputStreamObjectInputStream 等包装基础 InputStream,动态叠加缓冲、数据类型读取、对象序列化等功能。装饰器可以嵌套组合:new BufferedInputStream(new FileInputStream(f)) 叠加缓冲与文件读取。这种层次让功能可自由组合,是装饰器模式的经典应用。

InputStream 装饰器把"基础读取"与"增强功能"分层,可组合嵌套。经典装饰器案例。

#
★★

23. Supplier 作为工厂的函数式实现

请说明 Supplier 作为工厂的函数式实现?

  • Supplier 的延迟供应
  • 函数式工厂
  • 与工厂方法对比

Supplier<T> 是函数式接口,get() 延迟返回一个对象,可以作为"函数式工厂"——用 Lambda 封装对象创建逻辑,调用 get() 时才创建。相比传统工厂方法,Supplier 更简洁、可组合(可作为参数传递、结合函数式操作)。适用场景:延迟初始化、按需创建、注入工厂逻辑。它是工厂模式在函数式编程下的轻量实现。

Supplier 把"创建"封装为函数,延迟执行,是函数式工厂。与传统工厂相比更简洁灵活。

#
★★

24. 享元模式(Flyweight)的对象复用

请说明享元模式(Flyweight)的对象复用机制?

  • 共享与内部/外部状态
  • 复用减少内存
  • 适用场景

享元模式通过共享对象减少内存占用:把对象分为"内部状态"(不可变、可共享)与"外部状态"(由调用方传入)。享元工厂维护共享对象池,重复需求返回同一实例,外部状态作为参数传入。适合大量相似对象(如字符、连接池中的资源组件)。Integer 缓存、String 常量池是享元的体现。复用需注意内部状态不可变,否则共享不安全。

享元核心是"共享内部状态 + 外部状态分离"。复用前提是内部状态不可变,否则污染共享对象。

#
★★

25. 共同封闭原则(CCP)与包内聚

请说明共同封闭原则(CCP)与包内聚的关系?

  • CCP 的"一起变化的一起封装"
  • 包内聚
  • 与开闭原则

共同封闭原则(CCP)主张"一起变化的原因一起封闭",即把因同一原因变化的类放在同一个包中,使包内聚"变化性"。这样当需求变化时,只影响一个包,改动最小,符合开闭原则(对扩展开放、对修改封闭)。CCP 关注"包的变更粒度",包内聚是让包内类因同一原因变化,提升包的可维护性与可复用性。

CCP 是"包层面"的封内聚,按"变化原因"分组类。与 RE(重用)原则互补,权衡变化与重用。

#
★★

26. 共同重用原则(CRP)的判定标准是什么,违反时的依赖耦合问题如何识别与重构?

请说明共同重用原则(CRP)的判定标准,以及违反时的依赖耦合问题如何识别与重构?

  • CRP 的"一起重用的一起打包"
  • 违反时的依赖耦合
  • 识别与重构

共同重用原则(CRP)主张"一起被重用的类放在同一个包",判定标准是"使用这个包时,是否总是用到其中大部分类"——如果只用一个类却要依赖整个包,就违反 CRP。违反时的问题是依赖耦合:使用方不得不依赖包中不用的类,导致传递依赖、版本耦合、变更影响面大。识别:分析依赖图谱,看是否"只用到部分类却依赖整个包"。重构:按"重用粒度"拆分包,把高重用的类聚到一起。

CRP 关注"重用的粒度",违反导致"用一点依赖全部"。按使用方拆分包可解耦。

#
★★

27. 单一职责原则(SRP)的边界判断

请说明单一职责原则(SRP)的边界判断?

  • SRP 的"单一职责"
  • 职责的边界
  • 与类设计

单一职责原则(SRP)主张一个类只有一个引起变化的理由,即只承担一个职责。判断边界的关键是"变化的原因":如果两个职责因不同原因变化,应拆开。SRP 的边界不是固定的,取决于变化粒度——过度拆分会导致类爆炸,拆分不足导致职责混杂。判断标准:能否用一个"名词"描述类的职责,若有多个独立变化原因则拆分。

SRP 的边界以"变化原因"为准,是经验判断而非绝对规则。拆分的粒度要与实际变化对齐。

#
★★

28. 单例模式的破坏(反射、序列化、克隆)

请说明单例模式如何被反射、序列化、克隆破坏?

  • 反射破坏单例
  • 序列化破坏单例
  • 克隆破坏单例

单例模式可能被破坏:反射通过 setAccessible(true) 调用私有构造器创建新实例;序列化通过反序列化创建新实例(虽然构造器不执行);克隆通过 clone() 创建新实例。防御手段:序列化可用 readResolve() 防止反序列化破坏;构造器抛异常(或构造器内二次校验)防反射、禁止 clone 或枚举单例天然免疫。枚举单例因为 JVM 特殊处理,对这三者都免疫。

单例"保证唯一实例"的难点在于绕过正常构造的路径。反射/序列化/克隆是三大破坏入口。

#
★★

29. 单例模式(Singleton)的多种实现(饿汉/懒汉/双重检查/枚举)

请说明单例模式的多种实现(饿汉、懒汉、双重检查、枚举)?

  • 饿汉(类加载即创建)
  • 懒汉(延迟创建)
  • 双重检查锁与枚举

单例有多种实现:饿汉式在类加载时创建实例,线程安全但可能过早创建;懒汉式在使用时创建,非线程安全需加锁;双重检查锁(DCL)用双重判断 + volatile 保证线程安全与性能;枚举式最简洁可靠,天然线程安全且防反射/序列化。选择依据:是否需要延迟加载、线程安全要求、简洁性。通常推荐枚举或静态内部类(懒加载且线程安全)。

各实现权衡"初始化时机、线程安全、防破坏"。枚举与静态内部类在简洁与安全上占优。

#
★★

30. 原型模式(Prototype)与 clone() 的取舍

请说明原型模式(Prototype)与 clone() 的取舍?

  • clone() 的浅拷贝/深拷贝
  • 原型模式的适用
  • 与构造器新建对比

原型模式用 clone() 复制已有对象来创建新对象,而非通过构造器。取舍:clone() 适合"创建成本高或构造复杂"的对象,复制可避免重复初始化;但 clone() 默认浅拷贝,需深拷贝时需重写或手动处理引用字段,且 clone() 的语义(super.clone())易出错。相比构造器,clone 不执行构造器,可能绕过校验。适用场景:对象构造昂贵、配置复杂、需保留运行状态。现代 Java 更推荐用构造器、拷贝构造器或工厂而非 clone。

clone 的取舍在"复制成本 vs 深拷贝风险"。clone 浅拷贝是常见坑,需谨慎处理引用字段。

#
★★

31. 原型模式(Prototype)在 Spring @Scope("prototype") 与对象池(Object Pool)的工程价值

请说明原型模式(Prototype)在 Spring @Scope("prototype") 与对象池(Object Pool)的工程价值?

  • Spring Prototype 的按需新建
  • 对象池的复用
  • 二者适用场景

Spring 的 @Scope("prototype") 每次获取创建新实例,与原型模式目标一致(每次新对象),适合有状态、不可共享的 Bean;对象池(Object Pool)则强调复用对象(获取/归还/过期),适合创建成本高、可复用的对象(连接、线程)。二者工程价值不同:原型重在"新对象、隔离状态",对象池重在"复用、降开销"。选择取决于对象是否可复用、创建成本高低。

原型是"每次新建",对象池是"复用"。工程上按状态隔离与创建成本选择。

#
★★

32. 合成复用原则(CARP)与继承/组合的取舍,优先使用组合而非继承的原因

请说明合成复用原则(CARP)与继承/组合的取舍,以及优先使用组合而非继承的原因?

  • CARP 优先组合
  • 继承的耦合问题
  • 组合的灵活性

合成复用原则(CARP)主张优先使用组合(has-a)而非继承(is-a)来复用功能。原因:继承会破坏封装(子类依赖父类实现细节)、耦合强(父类变化影响子类)、违反 OCP(难以扩展)。组合通过持有对象引用、委托调用,灵活性高、松耦合、可动态替换。场景:当"is-a"关系成立且复用父类接口时用继承,否则用组合。组合利于测试与扩展。

合成复用优先是"松耦合复用"的共识。继承是强耦合,组合更灵活,但真 is-a 关系仍适合继承。

#
★★

33. 外观模式(Facade)的封装边界

请说明外观模式(Facade)的封装边界?

  • Facade 的统一接口
  • 封装边界
  • 与过度封装

外观模式为复杂子系统提供统一入口,封装边界是"对客户端暴露简洁接口,内部隐藏子系统复杂性"。但 Facade 不应过度封装:它应暴露合理粒度的接口,而非把所有内部细节都暴露或全部隐藏——过度封装会让客户端无法灵活使用子系统,封装不足则客户端仍要感知复杂。边界判断:Facade 提供"足够用、不过度"的接口,内部可扩展,客户端面向 Facade 而非子系统。

Facade 的边界是"简洁而不过度"。它简化调用,但不应限制必要的灵活性。

#
★★

34. 对象池模式(Object Pool)与连接池/线程池的共性设计(获取/归还/过期/泄漏检测),哪些场景不应使用对象池

请说明对象池模式(Object Pool)与连接池/线程池的共性设计,以及哪些场景不应使用对象池?

  • 对象池的获取/归还/过期/泄漏检测
  • 连接池、线程池的共性
  • 不适用场景

对象池(连接池、线程池是实例)的共性设计:获取(从池取可用对象)、归还(用后放回)、过期(超时回收)、泄漏检测(识别未归还的对象)、容量控制(最大/最小)。这些设计保证资源复用与生命周期。不应使用对象池的场景:对象创建成本低(池管理开销大于收益)、对象有状态且不可复用、池化导致资源泄漏风险高、或现代垃圾回收/虚拟线程已解决需求的场景(如虚拟线程池化无意义)。

对象池的收益取决于"创建成本 vs 池管理开销"。虚拟线程等新机制可能使池化过时。

#
★★

35. 工厂方法模式(Factory Method)的应用场景

请说明工厂方法模式(Factory Method)的应用场景?

  • 工厂方法将创建延迟到子类
  • 适用场景
  • 与扩展

工厂方法模式定义一个创建对象的接口,把实例化延迟到子类,适用场景:创建逻辑复杂或需要根据条件选择具体类;希望扩展性——新增产品类型时只需新增子类工厂,符合 OCP;需要统一创建入口(校验、日志)。典型如日志框架的 LoggerFactory、Spring 的 Bean 工厂。工厂方法让"创建"与"使用"解耦,客户端面向抽象产品。

工厂方法把"创建逻辑"封装、延迟到子类,适合"产品类型会扩展"的场景。

#
★★

36. 工厂模式(Factory Method)与抽象工厂(Abstract Factory)

请说明工厂方法模式(Factory Method)与抽象工厂(Abstract Factory)的区别?

  • 工厂方法创建单个产品
  • 抽象工厂创建产品族
  • 区别与选型

工厂方法模式用"一个工厂方法创建一个产品",通过子类覆盖工厂方法决定具体产品,是"一个产品一个工厂";抽象工厂模式提供"创建一组相关产品(产品族)"的接口,通过具体工厂创建一堆产品,是"一个工厂创建一族产品"。区别核心:工厂方法涉及单个产品,抽象工厂涉及产品族。场景:只需创建一种产品用工厂方法,需要创建一组配套产品用抽象工厂。

区别在"单产品 vs 产品族"。抽象工厂强调产品族的一致性,工厂方法强调单产品的多态创建。

#
★★

37. 建造者模式(Builder)与 Lombok @Builder 的取舍

请说明建造者模式(Builder)与 Lombok @Builder 的取舍?

  • Builder 的链式构建
  • Lombok @Builder 的简化
  • 取舍

建造者模式用 Builder 分步构建对象,适合参数多、可选参数多的对象,提升可读性(链式调用)。Lombok @Builder 自动生成 Builder 代码,减少手写样板代码,提升效率。取舍:手写 Builder 可控性强(可加校验、文档、兼容性),但代码冗长;Lombok @Builder 简洁但隐藏实现、不易定制校验、版本兼容需注意。对参数多且属性不可变的领域对象,Lombok @Builder 是高效选择。

Lombok 用注解生成 Builder,省样板但牺牲定制。取舍在"简洁性 vs 可控性"。

#
★★

38. 开闭原则(OCP)与扩展点设计

请说明开闭原则(OCP)与扩展点设计?

  • OCP 对扩展开放、对修改封闭
  • 扩展点设计
  • 与抽象

开闭原则(OCP)主张软件对扩展开放、对修改封闭——新增功能通过扩展而非修改已有代码。实现需要设计扩展点:抽象接口、策略/模板方法、策略注册表、SPI 机制等。扩展点设计让系统在"不修改核心"的前提下扩展行为。工程实践:面向接口编程、把变化点抽象为扩展点、用依赖注入/配置驱动。OCP 是目标,扩展点设计是手段。

OCP 靠"抽象 + 扩展点"实现。扩展点的位置决定系统是否易扩展,是架构前的关键设计。

#
★★

39. 抽象工厂模式(Abstract Factory)的产品族

请说明抽象工厂模式(Abstract Factory)的产品族?

  • 产品族的概念
  • 抽象工厂创建产品族
  • 一致性

抽象工厂模式围绕"产品族"展开:产品族是一组相关或依赖的产品对象(如一套 UI 组件、一套数据库访问对象)。抽象工厂定义创建产品族成员的接口,具体工厂创建一族配套产品,保证产品族内部一致性(如 Linux 工厂创建 Linux 风格按钮+文本框)。使用方通过抽象工厂获取整套产品,避免混用不兼容产品。典型如 UI 工具箱、跨平台组件。

抽象工厂的核心价值是"创建产品族并保证一致性"。新增产品族只需新增工厂,符合 OCP。

#
★★

40. 接口隔离原则(ISP)与角色接口

请说明接口隔离原则(ISP)与角色接口的关系?

  • ISP 的"最小接口"
  • 角色接口
  • 与胖接口

接口隔离原则(ISP)主张客户端不应依赖它不用的接口方法,应把胖接口拆分为多个专用接口,避免"实现者被迫实现不需要的方法"。角色接口是 ISP 的体现:按"角色"(客户端的使用视角)划分接口,每个角色定义一套最小方法,实现者按需实现多个角色接口。这样客户端只依赖所需角色的接口,接口更内聚、类更清晰。

ISP 反对胖接口,角色接口按客户端视角拆分。理解"客户端不应依赖不需要的方法"是核心。

#
★★

41. 无环依赖原则(ADP)与包分层

请说明无环依赖原则(ADP)与包分层?

  • ADP 的依赖无环
  • 包分层
  • 依赖方向

无环依赖原则(ADP)主张包之间的依赖关系不能形成环,否则会导致循环编译、难以独立测试与复用。包分层(如依赖自上而下)是消除环的手段:按抽象层次组织包,高层依赖低层,低层不依赖高层,形成无环依赖结构。若出现环,可引入新包把共同依赖抽离,或反转依赖方向。ADP 保证包的可独立构建与测试。

ADP 是"包依赖无环"的约束,分层是常见实现。环依赖破坏可复用性与构建独立性。

#
★★

42. 最少知识原则(Law of Demeter)

请说明最少知识原则(Law of Demeter)?

  • 最少知识原则的含义
  • 避免"链式对象"调用
  • 降低耦合

最少知识原则(Law of Demeter)主张"对象只与它直接相关的对象交互",不要通过链式调用获取深层对象(如 a.getB().getC().doX()),只应调用自身、方法参数、直接持有对象的方法。它降低对象间的耦合,减少因内部结构变化导致的连锁修改。代价是可能引入更多中间方法(封装转发),但提升了内聚与可维护性。

最少知识原则反对"火车链"调用,降低耦合。以"封装"换取"松耦合"。

#
★★

43. 枚举单例如何保证线程安全与防反序列化,与双重检查锁单例相比的取舍与最佳实践?

请说明枚举单例如何保证线程安全与防反序列化,以及与双重检查锁单例相比的取舍?

  • 枚举单例的线程安全
  • 防反序列化
  • 与双重检查锁对比

枚举单例的线程安全由 JVM 保证(枚举实例在类加载时创建一次,JVM 保证枚举的实例化是线程安全的);防反序列化因为枚举序列化有特殊处理,反序列化返回唯一实例(不创建新实例)。与双重检查锁(DCL)相比:DCL 需 volatile 与双重判空,实现繁琐且易出错;枚举单例代码简洁、天然安全、防反射与序列化。取舍:枚举虽不能继承其他类,但单例一般无需继承,因此枚举是优先选择。

枚举单例的"安全"优于 DCL,因为 JVM 原生保证。DCL 有 volatile 与初始化顺序的坑。

#
★★

44. 桥接模式(Bridge)的抽象与实现分离

请说明桥接模式(Bridge)的抽象与实现分离?

  • 抽象与实现分离
  • 独立变化
  • 与 JDBC 的关系

桥接模式把"抽象"与"实现"分离到两个维度,使二者可独立变化:抽象定义高层接口,实现定义底层操作,通过组合连接。这样"抽象"与"实现"各自可扩展而不互相影响。相比继承(抽象与实现绑定),桥接解开绑定,避免类爆炸。JDBC 是典型:SQL 接口(抽象)与不同数据库驱动(实现)分离,换数据库不改抽象。适用场景:抽象与实现都可能变化。

桥接的核心是"两个维度独立变化",用组合代替继承,避免维度组合造成类爆炸。

#
★★

45. 注册式单例(Container)的工程应用

请说明注册式单例(Container)的工程应用?

  • 注册式单例的容器管理
  • 按名称/类型获取
  • 与 Spring 容器

注册式单例(Container 单例)把单例注册到容器中,通过名称/类型获取,容器保证单例唯一性。Spring 的 IoC 容器就是注册式单例的典型:Bean 注册到容器,@Scope("singleton") 保证每个 Bean 名唯一实例,通过 getBean 获取。工程价值:统一管理多个单例、按需获取、生命周期管理、依赖注入。与普通单例(静态字段)相比,注册式单例由容器管理,更灵活、可测试。

注册式单例把"单例"托管给容器,Spring 是典型。相比静态单例,容器管理生命周期与依赖。

#
★★

46. 稳定依赖原则(SDP)与包依赖方向

请说明稳定依赖原则(SDP)与包依赖方向?

  • SDP 的稳定依赖
  • 依赖指向稳定
  • 依赖方向

稳定依赖原则(SDP)主张包应该依赖稳定的一方:依赖关系应指向更稳定的包,不稳定的包应依赖稳定的包,避免不稳定包被大量依赖。稳定度由"被依赖的频度"衡量,经常被依赖的包应该稳定(不易变化)。工程上,把经常变化的业务逻辑放在不稳定包,通用/基础设施放在稳定包,依赖方向指向稳定。这使变化影响面受控。

SDP 是"依赖指向稳定"的约束,与稳定抽象原则(SAP)配合描述包的角色。

#
★★

47. 稳定抽象原则(SAP)与抽象度

请说明稳定抽象原则(SAP)与抽象度?

  • SAP 的稳定与抽象
  • 抽象度
  • 与 SDP 的关系

稳定抽象原则(SAP)主张"稳定的包应抽象,抽象的包应稳定":一个包越稳定(被大量依赖),越应抽象(多为接口/抽象),这样变化时依赖方只依赖抽象,受实现变化影响小。抽象度衡量包中抽象类/接口的比例。SAP 与 SDP 互补:SDP 管"依赖方向指向稳定",SAP 管"稳定包要抽象",二者共同保证稳定包通过抽象吸收变化。

SAP 让"稳定"与"抽象"匹配,稳定包用接口隔离变化。抽象度是衡量指标。

#
★★

48. 简单工厂模式(Simple Factory)与工厂方法差异

请说明简单工厂(Simple Factory)与工厂方法(Factory Method)的差异?

  • 简单工厂的静态方法
  • 工厂方法的子类覆盖
  • 差异

简单工厂用一个静态方法(或单独类)根据参数创建不同产品,创建逻辑集中在一个地方,但新增产品需修改工厂方法,违反 OCP;工厂方法把创建延迟到子类,每个具体工厂创建一种产品,新增产品只需新增子类工厂,符合 OCP。差异核心:简单工厂是"集中创建、改工厂",工厂方法是"分散创建、加子类"。简单工厂更简单,工厂方法更符合开闭原则。

简单工厂简洁但扩展需改工厂;工厂方法可扩展但类更多。按扩展需求选择。

#
★★

49. JDK 动态代理与 CGLIB 的差异及对接口/类代理的适用边界

请说明 JDK 动态代理与 CGLIB 的差异及对接口/类代理的适用边界?

  • JDK 动态代理要求接口
  • CGLIB 子类代理
  • 适用边界与限制

JDK 动态代理基于接口,通过 Proxy 生成实现接口的代理类,要求目标类实现接口;CGLIB 通过生成目标类的子类(字节码)实现代理,不要求接口,但目标类不能是 final、方法不能是 final。适用边界:有接口优先 JDK 代理(Spring 默认),无接口或需要代理类用 CGLIB。Spring 在 AOP 中根据是否有接口选择。CGLIB 无法代理 final 类/方法,这是限制。

JDK 代理靠接口,CGLIB 靠子类化。选择取决于目标是否有接口、是否 final。

#

50. 组合模式(Composite)的树形结构

请说明组合模式(Composite)的树形结构?

  • 树形结构
  • 叶子与组合
  • 一致接口

组合模式把对象组织成树形结构,用"部分-整体"的层次表示。树节点(Composite)可包含子节点(叶子或组合),叶子是不可再分的操作单元。核心是"叶子与组合实现一致接口",调用方对单个对象与整个树用相同方式操作(如递归 count、render)。文件系统、UI 组件树是典型。组合模式统一了递归遍历与整体操作。

组合模式的核心是"部分与整体一致对待",树形结构让递归操作统一。

#

51. 调用反转原则(IoP)与控制流

请说明调用反转原则(IoP)与控制流?

  • IoP 的控制流反转
  • 框架调用
  • 与 IoC 的关系

调用反转原则(IoP)指控制流由框架/容器主导,而不是应用代码:框架在适当时机调用应用注册的回调/钩子,应用而非主动调用框架。这与 IoC(控制反转)相关,但 IoP 更强调"控制流"的转移(调用方向反转)。典型如 Spring 容器的生命周期回调、Servlet 的 doGet。IoP 让框架复用骨架、应用注入细节,反调用是核心。

IoP 强调"调用方向"反转,应用把控制权交给框架,框架回调应用。与好莱坞原则一致。

#

52. 过滤器模式(Filter)的责任链

请说明过滤器模式(Filter)的责任链?

  • Filter 的链式处理
  • 责任链的传递
  • 与拦截器

过滤器模式把多个过滤器组织成链,请求依次经过每个过滤器,每个过滤器可处理、拦截或放行到下一个,是责任链模式的应用。Java Servlet 的 Filter、Spring 的 WebFilter 是典型。过滤器链让横切关注点(日志、鉴权、编码)按顺序处理,可插拔、可组合。责任链的特点是"每个环节只处理自己能处理的,否则传给下一个"。

Filter 链是责任链的体现,过滤器按序处理、可短路。日志、鉴权等横切关注点用链组织。

#

53. 适配器模式(Adapter)的兼容价值

请说明适配器模式(Adapter)的兼容价值?

  • Adapter 的接口转换
  • 兼容性
  • 与重写

适配器模式(Adapter)把一个类的接口转换为客户端期望的另一个接口,解决"接口不兼容"问题,让原本不匹配的类能一起工作。兼容价值在于:无需修改现有代码即可让新接口与旧实现协作,保护既有实现、降低迁移成本。典型如把旧 API 适配为新接口、不同库的接口统一。适配器是"隔离层",让客户端面向期望接口,实现可以替换。

适配器是"接口转换器",解决不兼容,提升复用与兼容性。适配不改实现,只适配接口。

#

54. 里氏替换原则(LSP)与协变返回

请说明里氏替换原则(LSP)与协变返回?

  • LSP 的可替换性
  • 协变返回
  • 与继承

里氏替换原则(LSP)主张子类应能替换父类而不破坏正确性,即子类必须保持父类的行为契约(不弱化前置条件、不强化后置条件)。协变返回是 Java 支持的一个特性:子类重写方法时可以返回父类方法的子类型,这符合 LSP——返回更具体的类型不破坏替换性。LSP 是继承正确性的基石,违反 LSP 会导致替换后行为异常。

LSP 保证"is-a 替换",协变返回是其中的合法扩展。破坏 LSP 的继承(如菜鸟变恶龙)是设计问题。

#

55. 重用发布等价原则(REP)

请说明重用发布等价原则(REP)?

  • REP 的重用粒度与发布粒度
  • 包与发布
  • 与版本

重用发布等价原则(REP)主张"重用的粒度与发布的粒度一致":一个包中内容应一起发布、一起被重用,若被重用,应作为一个发布单元(含版本)。即包的边界应与其"作为可发布制品"的边界一致,避免"用一部分却要发布整个包"。REP 影响包设计与版本管理,使包内聚可重用、可版本化。

REP 让"重用单元"与"发布单元"对齐,是包粒度与制品管理的关系。

#

56. DRY(Don't Repeat Yourself)的工程取舍

请说明 DRY(Don't Repeat Yourself)的工程取舍?

  • DRY 的避免重复
  • 抽象抽取
  • 过度抽象

DRY(Don't Repeat Yourself)主张"避免重复,每个知识只表达一次",通过抽取公共逻辑消除重复,提升可维护性。但工程取舍:过度 DRY 会过早抽象,把"碰巧相似"但"变化不同"的代码强行合并,导致耦合与抽象成本;完全忽略 DRY 会导致重复代码难以维护。取舍在于:判断重复是"真正的重复"(同一知识)还是"偶然相似",对真正的重复抽象,对偶然相似保留差异。

DRY 的取舍在"避免重复 vs 避免过抽象"。真正的重复(同一语义)应抽取,偶然相似不宜强行合并。

#

57. JDK 25 中对象池(Object Pool)模式与虚拟线程协作的简化实现

请说明 JDK 25 中对象池(Object Pool)模式与虚拟线程协作的简化实现?

  • 对象池与虚拟线程
  • 协作简化
  • 池化考量

在 JDK 25 虚拟线程环境下,对象池的协作可以简化:虚拟线程轻量、创建成本低,很多"为复用线程而池化"的场景(如线程池)不再必要,因为虚拟线程可大量创建。但对象池(如连接池、昂贵对象)仍有价值——池化的是"昂贵的资源"而非"线程"。虚拟线程让"每任务一线程"的模式可行,同时对象池针对连接等资源仍复用。协作:虚拟线程处理并发,对象池管理稀缺资源,二者职责分离。

虚拟线程消除"线程池化"需求,但对象池针对资源复用仍必要。理解"池化对象"与"虚拟线程"分工。

#

58. KISS(Keep It Simple, Stupid)的过度简化与平衡

请说明 KISS(Keep It Simple, Stupid)的过度简化与平衡?

  • KISS 的简单性
  • 过度简化
  • 平衡

KISS(Keep It Simple, Stupid)主张保持设计简单,避免不必要的复杂度。但过度简化会牺牲必要的能力与可扩展性(如砍掉必要的错误处理、过度省略导致不可用)。平衡在于:简单是"消除不必要的复杂度",而非"削减必要功能";复杂度应匹配问题本身的复杂度。KISS 与 YAGNI 结合,但在需求明确需要时保留必要的复杂度。

KISS 的平衡是"简单但不简陋"。区分"必要复杂度"与"过度设计",简单性服务于可维护性。

#

59. SOLID 五原则(SRP/OCP/LSP/ISP/DIP)的工程价值

请说明 SOLID 五原则(SRP/OCP/LSP/ISP/DIP)的工程价值?

  • 五原则各自内容
  • 工程价值
  • 协同

SOLID 五原则:SRP(单一职责)、OCP(开闭)、LSP(里氏替换)、ISP(接口隔离)、DIP(依赖倒置)。工程价值:SRP 让类职责单一易维护;OCP 让系统易扩展;LSP 保证继承正确;ISP 让接口内聚;DIP 让依赖抽象。五原则共同提升代码的可维护性、可扩展性、可测试性,是面向对象设计的基石。它们协同:SRP 定位职责,OCP 指导扩展,DIP 指导依赖方向。

SOLID 是 OOP 设计的核心原则,分别从职责、扩展、继承、接口、依赖五个维度约束设计。

#

60. YAGNI(You Aren't Gonna Need It)的预判

请说明 YAGNI(You Aren't Gonna Need It)的预判?

  • YAGNI 的"不需要"
  • 避免过度设计
  • 与需求

YAGNI(You Aren't Gonna Need It)主张不要为"可能未来需要"的功能提前设计和实现,避免过度设计引入不必要的复杂度与维护成本。预判的关键:区分"明确需求"与"臆测需求",只实现当前需要的能力,通过重构应对未来变化。YAGNI 与 KISS 互补,但需平衡——在架构上预留合理的扩展点(如接口)是必要的,但具体实现按需。过早抽象是 YAGNI 反对的。

YAGNI 反对"臆测未来"的过度设计,但允许合理的架构预留。预判是"需求驱动的实现"。

#

61. 建造者模式(Builder)在 Lombok @Builder 与 JDK 25 记录类(record)的取舍

请说明建造者模式(Builder)在 Lombok @Builder 与 JDK 记录类(record)的取舍?

  • 记录类(record)的不可变
  • Lombok @Builder 的构建
  • 取舍

JDK 记录类(record)天然不可变、简洁(自动生成构造器、equals、hashCode、toString),适合作为不可变数据载体。Lombok @Builder 用于对象分步构建,与 record 结合时,record 的紧凑构造器 + @Builder 可生成 builder。取舍:record 强调不可变与简洁,@Builder 侧重建构的可读性。对"参数多、需链式构建"的对象用 builder;对"简单不可变数据"用 record 更简洁。record + @Builder 可结合,但 record 的紧凑构造器限制了校验。

record 是"声明式不可变数据",@Builder 是"构建式"。按对象复杂度与构建需求选择。

#

62. 空对象模式(Null Object)与 Optional 在消除空指针上的取舍

请说明空对象模式(Null Object)与 Optional 在消除空指针上的取舍?

  • Null Object 的默认行为
  • Optional 的显式空值
  • 取舍

空对象模式(Null Object)提供一个"什么都不做"的默认对象,替代 null,调用方无需判空,调用安全;Optional 用容器显式表达"可能有值也可能没有",强制调用方处理空值。取舍:Null Object 适合"有合理默认行为"的场景(如默认日志器、空集合),消除判空;Optional 适合"返回可能为空、需调用方决策"的场景,让空值显式、可链式操作。Null Object 隐式消空,Optional 显式提空。

Null Object 隐藏空值(默认行为),Optional 暴露空值(调用方处理)。按"是否有默认行为"选择。