🎤 高频面试题
单例破坏与防护 · 六大原则 · 模式对比 · 场景设计题
1. 反射和序列化如何破坏单例?怎么防?
- 反射破坏:setAccessible(true) 调用私有构造器 new 出第二个实例。
- 防:构造器里判断实例已存在则抛异常(但反射仍可先生成两个)
- 终极方案:枚举——枚举构造器是 JVM 层面的单实例,反射 newInstance 对枚举直接抛异常(源码判断 isEnum)
- 序列化破坏:反序列化会 new 一个新对象(不走构造器)。
- 防:实现 readResolve() 方法返回原实例
- 枚举天然免疫:枚举序列化由 JVM 特殊处理,反序列化返回同一实例
readResolve 防序列化
class Singleton implements Serializable {
private static final Singleton INSTANCE = new Singleton();
private Singleton() {}
// 反序列化时被调用,返回已有实例(防止新对象)
private Object readResolve() { return INSTANCE; }
}
🎯 面试要点
- 能背出"枚举的三个防护点"(构造、序列化、反射)就是完整答案
- 克隆(clone)也会绕过构造器——单例类不要实现 Cloneable
2. 面向对象设计六大原则(SOLID + 迪米特)?
- 单一职责(S):一个类只做一件事,变化只影响一处
- 开闭原则(O):对扩展开放、对修改关闭(核心原则)——用策略/模板/装饰器实现
- 里氏替换(L):子类必须能替换父类且行为不破坏(重写不能削弱父类能力)
- 接口隔离(I):接口小而专,别搞万能接口(对比:接口大而全违反)
- 依赖倒置(D):依赖抽象不依赖具体(高层不依赖低层,都依赖抽象)——IoC 的理论基础
- 迪米特法则:最少知道原则——只和朋友通信,降低耦合
🎯 面试要点
- 必考组合:依赖倒置 ↔ Spring IoC;开闭 ↔ 策略/装饰器/工厂
- 里氏替换举例:父类方法抛 1 个异常,子类不能抛更宽泛的(Java 重写规则就体现它)
3. 易混模式对比速查?
| 对比组 | 一句话区分 |
|---|---|
| 代理 vs 装饰器 | 代理管"调不调/何时调"(控制访问);装饰器管"调的过程加什么"(增强功能) |
| 适配器 vs 外观 | 适配器转换接口(让不兼容变兼容);外观提供统一简化入口(隐藏复杂度) |
| 策略 vs 状态 | 策略:外部选算法,对象行为不自动变;状态:内部状态驱动行为自动迁移 |
| 策略 vs 模板方法 | 策略整体算法互换(组合);模板方法固定骨架细节可改(继承) |
| 工厂方法 vs 抽象工厂 | 工厂方法:一个工厂生产一种产品;抽象工厂:一个工厂生产一族产品 |
| 责任链 vs 装饰器 | 责任链横向传递(请求依次经过);装饰器纵向包裹(调用嵌套) |
🎯 面试要点
- 对比题的答题公式:"相同点(结构相似)+ 不同点(意图/控制权)+ 各自场景"
4. 场景设计题:该用哪个模式?
| 场景 | 推荐模式 |
|---|---|
| 多支付方式(微信/支付宝/银联) | 策略 + 工厂 + 注册表 |
| 下单后发短信/通知/记账 | 观察者(事件驱动),异步化 |
| 日志框架切换实现 | 外观 + 适配器(slf4j 门面) |
| 请求校验链(登录→权限→限流→业务) | 责任链(可中断) |
| 订单状态流转(待支付→已支付…) | 状态模式 + 状态机表 |
| DB 操作统一模板(连接/事务/异常) | 模板方法(JdbcTemplate) |
| 对象创建开销大且频繁复用 | 享元(池化) |
🎯 面试要点
- 场景题答题套路:识别"变化点" → 选择隔离变化的原则 → 给出模式结构 → 说明收益(解耦/扩展)