1. 什么是设计模式?为什么要使用设计模式?
答:
设计模式是软件开发中针对常见问题的可复用解决方案 ,是对优秀代码设计经验的总结。它不是具体的代码,而是一种思想或模板。
使用目的:
- 提高代码的可重用性、可读性和可维护性
- 降低模块间的耦合度
- 增强系统的扩展性与灵活性
- 帮助开发者快速理解大型框架(如 Spring、MyBatis)的设计思想
2. 设计模式分为哪几类?各有哪些典型模式?
答:
设计模式共23种,分为三类:
- 创建型(5种):单例、工厂方法、抽象工厂、建造者、原型
- 结构型(7种):适配器、装饰器、代理、外观、桥接、组合、享元
- 行为型(11种):策略、模板方法、观察者、责任链、命令、状态、访问者、中介者、备忘录、迭代器、解释器
3. 请手写一个线程安全且支持延迟加载的单例模式。
答:(推荐静态内部类方式)
public class Singleton {
private Singleton() {}
private static class Holder {
private static final Singleton INSTANCE = new Singleton();
}
public static Singleton getInstance() {
return Holder.INSTANCE;
}
}
✅ 优点:线程安全、延迟加载、简洁高效、利用 JVM 类加载机制保证唯一性。
4. 单例模式如何防止反射攻击?
答:
在构造方法中添加校验标志:
public class Singleton {
private static boolean initialized = false;
private Singleton() {
synchronized (Singleton.class) {
if (initialized) {
throw new RuntimeException("单例已被实例化,禁止反射创建!");
}
initialized = true;
}
}
// ... 其他实现(如静态内部类)
}
⚠️ 注意:枚举单例天然防反射,是最安全的方式。
5. 工厂模式有哪几种?它们的区别是什么?
答:
| 类型 | 特点 | 缺点 |
|---|---|---|
| 简单工厂 | 一个工厂类通过参数创建不同产品 | 违反开闭原则,新增产品需修改工厂 |
| 工厂方法 | 每个产品对应一个工厂子类 | 类数量增多,但符合开闭原则 |
| 抽象工厂 | 创建产品族(多个相关产品) | 难以支持新产品的增加(需修改接口) |
✅ Spring 的
BeanFactory是简单工厂 + 反射的典型应用。
6. JDK 动态代理和 CGLIB 代理有什么区别?
答:
| 对比项 | JDK 动态代理 | CGLIB 代理 |
|---|---|---|
| 实现方式 | 基于接口,使用 Proxy 类 |
基于继承,生成目标类的子类 |
| 是否需要接口 | 必须有接口 | 不需要接口 |
| 能否代理 final 类/方法 | 可以(只要实现接口) | ❌ 不能代理 final 类或方法 |
| 性能 | 较快(JDK 优化好) | 略慢(需生成字节码) |
| 依赖 | JDK 自带 | 需引入 cglib 或 spring-core |
✅ Spring AOP 默认:有接口 → JDK 代理;无接口 → CGLIB。
7. 什么场景下使用建造者模式?它和工厂模式有何区别?
答:
适用场景:
- 对象构造参数多(≥4)
- 参数中有可选字段
- 对象内部属性存在依赖或约束
与工厂模式区别:
- 工厂模式 :关注"创建什么对象"(产品种类)
- 建造者模式 :关注"如何一步步构建复杂对象"(构建过程)
🌰 例子:
StringBuilder、OkHttpClient.Builder、订单创建(含地址、商品、优惠券等)
8. 模板方法模式的核心思想是什么?请举例说明。
答:
核心思想:在父类中定义算法骨架(固定流程),将可变步骤延迟到子类实现。
例子:
-
Servlet 中的
service()调用doGet()/doPost() -
JUnit 测试生命周期:
setUp()→testXxx()→tearDown() -
数据库操作模板:连接 → 执行 SQL → 处理结果 → 关闭连接
abstract class DataProcessor {
public final void process() { // 模板方法(final 防止被覆盖)
connect();
processData(); // 子类实现
disconnect();
}
abstract void processData();
}
9. 策略模式解决了什么问题?如何避免大量 if-else?
答:
解决的问题 :将一组可互换的算法封装起来,使它们可以相互替换,消除复杂的条件判断。
实现方式:
-
定义策略接口
-
每种算法实现一个策略类
-
上下文(Context)持有策略并调用
// 支付策略
interface PaymentStrategy { void pay(double amount); }
class WechatPay implements PaymentStrategy { ... }
class Alipay implements PaymentStrategy { ... }// 使用
PaymentContext ctx = new PaymentContext(new WechatPay());
ctx.execute(100);
✅ 避免了 if(type == "wechat") ... else if(type == "alipay") ...
10. 观察者模式的应用场景有哪些?推模式和拉模式有何区别?
答:
应用场景:
- GUI 事件监听(按钮点击)
- 消息订阅系统(MQ、EventBus)
- Spring 事件机制(
ApplicationEvent) - 配置中心动态刷新
推 vs 拉:
- 推模式 :主题主动将完整数据推送给观察者(耦合高,效率高)
- 拉模式 :只通知"有变化",观察者主动拉取所需数据(解耦好,灵活)
11. 外观模式(门面模式)的作用是什么?请举例。
答:
作用 :为复杂的子系统提供一个统一、简化的高层接口,隐藏内部细节。
例子: 用户注册成功后需:
- 发短信(AliSmsService)
- 发邮件(EmailService)
- 推送微信消息(WechatService)
使用外观模式封装为:
public class NotificationFacade {
public void sendWelcomeMessage(String user) {
sms.send(user);
email.send(user);
wechat.push(user);
}
}
客户端只需调用 sendWelcomeMessage(),无需关心内部调用逻辑。
12. 原型模式中的深拷贝和浅拷贝有什么区别?如何实现深拷贝?
答:
| 类型 | 行为 | 风险 |
|---|---|---|
| 浅拷贝 | 基本类型复制值,引用类型复制地址 | 修改副本会影响原对象 |
| 深拷贝 | 所有对象(包括引用)都新开内存复制 | 完全独立,互不影响 |
实现深拷贝方式:
-
重写
clone()并递归克隆引用对象(如list.clone()) -
使用序列化/反序列化(推荐,适用于复杂对象)
// 序列化实现深拷贝
ByteArrayOutputStream bos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bos);
oos.writeObject(obj);ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bis);
return (User) ois.readObject();
13. Spring 中哪些地方用到了设计模式?
答:
- 单例模式:默认 Bean 作用域为 singleton
- 工厂模式 :
BeanFactory、ApplicationContext - 代理模式:AOP(JDK/CGLIB 动态代理)
- 模板方法 :
JdbcTemplate、RestTemplate - 观察者模式 :
ApplicationEvent事件机制 - 策略模式 :
ResourceLoader根据前缀选择不同资源加载策略 - 适配器模式 :
HandlerAdapter适配不同 Controller 类型
14. 设计模式的六大原则是什么?请简要说明。
答:(SOLID + LoD)
- 单一职责原则(SRP):一个类只负责一个职责
- 开闭原则(OCP):对扩展开放,对修改关闭
- 里氏替换原则(LSP):子类可替换父类而不影响功能
- 接口隔离原则(ISP):接口要小而专,避免臃肿
- 依赖倒置原则(DIP):面向接口编程,依赖抽象而非具体
- 迪米特法则(LoD):最少知道原则,降低类间耦合
✅ 这些原则是设计模式的理论基础,也是高质量 OOP 设计的核心准则。