设计模式
设计模式是常见软件设计问题的经验总结。学习设计模式不是为了套模板,而是为了在合适场景下写出更稳定、可扩展、可维护的代码。
为什么要学设计模式
如果所有代码都按“想到哪写到哪”的方式增长,项目会逐渐出现这些问题:
| 问题 | 后果 |
|---|---|
| 重复 if/else | 新增规则要到处改,容易漏 |
| 对具体类强依赖 | 替换实现困难 |
| 横切逻辑散落 | 日志、权限、事务代码重复 |
| 主流程太臃肿 | 注册、下单等方法越来越长 |
| 创建逻辑分散 | 对象初始化规则难统一 |
设计模式的价值是识别变化点,并把变化隔离在合适的位置。
mermaid
flowchart TD
A["发现变化点"] --> B{"变化是否真实稳定"}
B -- "否" --> C["先保持简单"]
B -- "是" --> D["选择合适模式"]
D --> E["隔离变化"]
E --> F["让新增功能少改老代码"]分类
mermaid
flowchart TD
A["设计模式"] --> B["创建型<br/>解决对象创建"]
A --> C["结构型<br/>解决对象组合"]
A --> D["行为型<br/>解决行为协作"]
B --> E["单例 / 工厂 / 建造者"]
C --> F["代理 / 装饰器 / 适配器"]
D --> G["策略 / 观察者 / 模板方法"]学习路线
学习设计模式建议按“为什么要拆 -> 拆到哪里 -> 不拆会怎样 -> 拆过头会怎样”理解,不要先背类图。
建议先读总课程页:
mermaid
flowchart TD
A["先识别变化点"] --> B["创建型:对象怎么创建"]
B --> C["结构型:对象怎么组合增强"]
C --> D["行为型:流程和规则怎么协作"]
D --> E["结合 Spring / JDK / 商业场景"]
E --> F["最后看面试表达"]本目录
| 模式 | 解决什么问题 | 页面 |
|---|---|---|
| 总验收 | 判断什么时候该用、怎么用、用错怎么排查 | 从零到精通验收清单 |
| 单例模式 | 全局唯一对象和共享资源管理 | 单例模式 |
| 工厂模式 | 封装对象创建和选择逻辑 | 工厂模式 |
| 建造者模式 | 分步骤构建复杂对象,避免长构造器 | 建造者模式 |
| 代理模式 | 在访问目标对象前后统一增强或控制 | 代理模式 |
| 装饰器模式 | 不修改原类,动态叠加能力 | 装饰器模式 |
| 适配器模式 | 把不兼容接口转换成统一接口 | 适配器模式 |
| 策略模式 | 多种算法或业务规则切换 | 策略模式 |
| 观察者模式 | 一对多事件通知和扩展 | 观察者模式 |
| 模板方法模式 | 固定流程骨架,开放变化步骤 | 模板方法模式 |
| 责任链模式 | 多个处理器按顺序处理请求 | 责任链模式 |
怎么选择模式
mermaid
flowchart TD
A["遇到设计问题"] --> B{"问题是对象创建吗"}
B -- "是" --> C["工厂 / 单例 / 建造者"]
B -- "否" --> D{"问题是功能增强或访问控制吗"}
D -- "是" --> E["代理 / 装饰器 / 适配器"]
D -- "否" --> F{"问题是多种规则切换吗"}
F -- "是" --> G["策略"]
F -- "否" --> H{"问题是一件事后通知多方吗"}
H -- "是" --> I["观察者"]
H -- "否" --> J{"问题是固定流程或校验链吗"}
J -- "固定流程" --> K["模板方法"]
J -- "校验链" --> L["责任链"]
J -- "都不是" --> M["先保持简单"]选择模式时要先问:
- 变化点是否真实存在。
- 变化是否会持续发生。
- 抽象后是否减少修改面。
- 新人是否能快速理解。
- 测试是否更容易。
Demo:策略模式消除 if/else
折扣规则会持续扩展时,可以用策略模式。
java
public interface DiscountStrategy {
String type();
BigDecimal discount(BigDecimal originPrice);
}java
@Component
public class VipDiscountStrategy implements DiscountStrategy {
@Override
public String type() {
return "VIP";
}
@Override
public BigDecimal discount(BigDecimal originPrice) {
return originPrice.multiply(new BigDecimal("0.8"));
}
}java
@Service
public class DiscountService {
private final Map<String, DiscountStrategy> strategyMap;
public DiscountService(List<DiscountStrategy> strategies) {
this.strategyMap = strategies.stream()
.collect(Collectors.toMap(DiscountStrategy::type, strategy -> strategy));
}
public BigDecimal calculate(String memberType, BigDecimal originPrice) {
DiscountStrategy strategy = strategyMap.get(memberType);
if (strategy == null) {
return originPrice;
}
return strategy.discount(originPrice);
}
}这个 Demo 体现了设计模式的使用边界:当折扣规则确实会扩展时,策略模式能隔离变化;如果只有一个规则,就没必要提前抽象。
不要过度设计
设计模式不是越多越好。简单业务直接写清楚即可,只有当变化点明确、重复逻辑明显、扩展压力真实存在时,再考虑引入模式。
| 过度设计表现 | 后果 |
|---|---|
| 一两个实现也抽接口、工厂、策略 | 文件变多,跳转成本上升 |
| 业务还不稳定就抽象 | 抽象很快失效,反而难改 |
| 为了模式而模式 | 代码看起来高级,实际更难维护 |
| 所有逻辑都事件化 | 调用链分散,排查困难 |
学习方法
每个模式都按以下顺序理解:
- 它解决什么问题。
- 如果不用会怎样。
- 它的角色有哪些。
- 它的流程是什么。
- 它带来什么好处。
- 它有什么代价。
- 在 Spring、JDK 或业务中哪里见过。
商业项目常见组合
| 场景 | 常见组合 | 为什么 |
|---|---|---|
| 支付方式选择 | 工厂 + 策略 | 工厂找到支付策略,策略执行支付 |
| 订单创建校验 | 责任链 + 策略 | 校验链控制顺序,不同业务规则独立 |
| 数据导入 | 模板方法 + 责任链 | 模板固定导入流程,责任链做校验 |
| 第三方医院接口 | 适配器 + 策略 | 适配器屏蔽协议差异,策略选择医院实现 |
| Spring 事务 | 代理模式 | 方法调用经过代理,统一开启提交回滚 |
| Java IO | 装饰器模式 | 一层层包装输入输出能力 |
面试回答总模板
text
我理解设计模式不是为了套类图,而是为了隔离稳定变化点。比如同一业务有多种算法时用策略,创建实现类变化时用工厂,复杂对象参数多时用建造者,横切增强用代理,第三方接口不兼容用适配器,固定流程开放步骤用模板方法,多校验步骤按顺序执行用责任链。使用时也要避免过度设计,只有变化真实存在、抽象能减少修改面时才引入。设计模式不是背 UML,而是训练判断力:看到变化点,能选择合适的隔离方式;看到简单问题,也能克制不乱抽象。
