如何使用模板方法模式进行代码重构?
面试场景
面试官:”你在项目中做过代码重构吗?能举个例子吗?”
回答这个问题的高分框架:
- 业务背景
- 原代码的问题
- 重构的思考过程
- 具体实现
- 可量化的效果
业务场景
某物联网系统,接入十几种设备(风机、光伏板、储能电池等),每种设备的数据处理流程相似:
数据采集 → 数据统计 → 阈值监控 → 异常告警 → 创建工单
原代码问题
代码现状:每种设备各自实现了完整流程,80%-90%代码重复。
public class WindTurbine {
public void process() {
// 采集数据 - 100行代码
collectData();
// 统计汇总 - 80行代码
statisticData();
// 阈值监控 - 50行代码(各设备不同)
monitor();
// 异常告警 - 60行代码
alarm();
// 创建工单 - 70行代码
createWorkOrder();
}
}
public class SolarPanel {
public void process() {
// 几乎一样的代码,复制粘贴...
}
}
public class Battery {
public void process() {
// 又是复制粘贴...
}
}
问题:
- 通用逻辑修改需要改十几个类
- 容易遗漏、出错
- 代码臃肿,难以维护
识别Bad Smell
根据《重构》一书的”代码坏味道”:
| Bad Smell | 表现 | 危害 |
|---|---|---|
| 重复代码 | 相同逻辑出现多处 | 维护成本高 |
| 过长方法 | 几百行的process() | 难以理解 |
| 霰弹式修改 | 改一个功能要改多处 | 容易遗漏 |
重构方案:模板方法模式
模式定义
模板方法模式:定义一个操作的算法骨架,将某些步骤延迟到子类中实现。子类可以在不改变算法结构的情况下,重新定义算法的某些步骤。
类结构
AbstractDevice(抽象父类)
│
├── process() // 模板方法,定义流程
├── collectData() // 具体方法,可复用
├── statisticData() // 具体方法,可复用
├── monitor() // 抽象方法,子类必须实现
├── alarm() // 具体方法,可复用
└── createWorkOrder() // 具体方法,可复用
▲
│
┌────┴────┬────────┬────────┐
│ │ │ │
WindTurbine SolarPanel Battery ...
└── monitor() // 只需实现差异部分
重构后代码
抽象父类
public abstract class AbstractDevice {
/**
* 模板方法 - 定义处理流程
*/
public final void process() {
collectData();
statisticData();
monitor();
alarm();
createWorkOrder();
}
/**
* 具体方法 - 通用逻辑,子类可直接复用
*/
protected void collectData() {
// 数据采集通用实现
log.info("开始采集设备数据");
// ...
}
protected void statisticData() {
// 数据统计通用实现
log.info("开始统计汇总");
// ...
}
/**
* 抽象方法 - 各设备监控逻辑不同,必须由子类实现
*/
protected abstract void monitor();
protected void alarm() {
// 告警通用实现
log.info("发送异常告警");
// ...
}
protected void createWorkOrder() {
// 创建工单通用实现
log.info("创建处理工单");
// ...
}
}
具体子类
// 风机设备
public class WindTurbine extends AbstractDevice {
@Override
protected void monitor() {
// 风机特有的监控逻辑
if (rpm > maxRpm) {
log.warn("风机转速异常: {}", rpm);
}
if (temperature > maxTemperature) {
log.warn("风机温度异常: {}", temperature);
}
}
}
// 光伏板设备
public class SolarPanel extends AbstractDevice {
@Override
protected void monitor() {
// 光伏板特有的监控逻辑
if (efficiency < minEfficiency) {
log.warn("光伏板效率异常: {}", efficiency);
}
}
}
// 储能电池
public class Battery extends AbstractDevice {
@Override
protected void monitor() {
// 电池特有的监控逻辑
if (soc < 10 || soc > 95) {
log.warn("电池电量异常: {}", soc);
}
if (temperature > 45) {
log.warn("电池温度异常: {}", temperature);
}
}
}
钩子方法
如果某些子类需要定制非抽象方法,可以使用钩子方法:
public abstract class AbstractDevice {
public final void process() {
collectData();
statisticData();
monitor();
// 钩子方法 - 默认空实现,子类可选择性覆盖
beforeAlarm();
alarm();
createWorkOrder();
// 钩子方法
afterCreateWorkOrder();
}
/**
* 钩子方法 - 默认空实现
*/
protected void beforeAlarm() {
// 子类可以覆盖
}
protected void afterCreateWorkOrder() {
// 子类可以覆盖
}
}
// 某些设备需要特殊处理
public class SpecialDevice extends AbstractDevice {
@Override
protected void monitor() {
// ...
}
@Override
protected void beforeAlarm() {
// 告警前的特殊处理
notifyManager();
}
}
模板方法 vs 策略模式
| 对比项 | 模板方法模式 | 策略模式 |
|---|---|---|
| 关系 | 继承 | 组合 |
| 变化点 | 算法的某些步骤 | 整个算法 |
| 使用时机 | 流程固定,步骤有差异 | 算法可替换 |
| 类数量 | 子类多 | 策略类多 |
重构效果
代码量对比
| 指标 | 重构前 | 重构后 | 减少 |
|---|---|---|---|
| 总方法数 | 70个 | 19个 | 73% |
| 重复代码行 | 2800行 | 0行 | 100% |
| 修改点(改告警逻辑) | 14处 | 1处 | 93% |
研发效率
- 新增设备类型:从3天减少到0.5天
- Bug修复:避免遗漏,质量提升
面试答题框架
业务背景:物联网系统,十几种设备,处理流程相似
代码问题:
- 80%代码重复
- 修改一处需改十几个类
- 容易遗漏出错
重构方案:模板方法模式
- 抽象父类定义流程(模板方法)
- 通用逻辑放父类(具体方法)
- 差异逻辑放子类(抽象方法)
- 可选扩展点(钩子方法)
重构效果:
- 方法数减少73%
- 新增设备从3天到0.5天
- 消除重复代码
总结
| 要点 | 说明 |
|---|---|
| 适用场景 | 流程固定,步骤有差异 |
| 核心思想 | Don’t call us, we’ll call you(好莱坞原则) |
| 抽象方法 | 子类必须实现 |
| 钩子方法 | 子类可选择性覆盖 |
| 具体方法 | 父类实现,子类复用 |