20、大型重构(二):分离领域与表现层、提炼继承体系
大型重构做到这一步,其实已经进入深水区了。
上一章我们聊了如何拆分上帝类、梳理模块边界。今天要说的这两个动作——分离领域与表现层、提炼继承体系——是真正让系统「脱胎换骨」的关键步骤。
我个人习惯把这两步放在一起做。为什么呢?因为领域逻辑一旦从表现层抽离出来,你会发现很多类之间天然存在「父子关系」或「兄弟关系」,这时候提炼继承体系就顺理成章了。
一、先聊聊「分离领域与表现层」
说白了,就是把业务逻辑和界面代码拆开。
我在项目中遇到过一种典型场景:一个订单管理页面,里面直接写了计算折扣、校验库存、生成物流单号的代码。页面一多,同样的逻辑散落在十几个地方。改一个折扣规则,得把所有页面翻一遍——这种痛,经历过的人都懂。
核心原则:表现层只负责「展示」和「交互」,不负责「决策」和「计算」。
具体怎么做?我建议分三步走:
- 识别「领域知识」——哪些代码是在描述业务规则?比如「满100减20」、「VIP用户免运费」。
- 提取领域服务——把这些逻辑封装成独立的类或方法,不依赖任何UI组件。
- 表现层只调用——页面里只保留事件绑定、数据绑定、状态切换,所有业务逻辑都委托给领域层。
举个例子,重构前可能是这样的:
// 重构前:表现层和领域逻辑混在一起
public class OrderPage {
public void submitOrder(Order order) {
// 计算折扣(领域逻辑)
if (order.getTotal() > 100) {
order.setDiscount(20);
}
// 校验库存(领域逻辑)
if (inventoryService.checkStock(order) == false) {
showError("库存不足");
return;
}
// 保存订单(表现层逻辑)
saveToDatabase(order);
showSuccess("下单成功");
}
}
重构后,领域逻辑被抽到独立的服务里:
// 重构后:领域层
public class OrderDomainService {
public void applyDiscount(Order order) {
if (order.getTotal() > 100) {
order.setDiscount(20);
}
}
public boolean validateStock(Order order) {
return inventoryService.checkStock(order);
}
}
// 表现层只负责调用
public class OrderPage {
private OrderDomainService domainService;
public void submitOrder(Order order) {
domainService.applyDiscount(order);
if (!domainService.validateStock(order)) {
showError("库存不足");
return;
}
saveToDatabase(order);
showSuccess("下单成功");
}
}
小提示:分离之后,领域逻辑可以独立测试。你想想看,不用打开浏览器、不用登录页面,直接写单元测试就能验证折扣规则——这效率提升可不是一星半点。
二、再来说说「提炼继承体系」
领域逻辑抽出来之后,你可能会发现:有些类长得太像了。
比如,我们有「普通订单处理器」、「VIP订单处理器」、「企业订单处理器」。它们都有相同的校验逻辑、相同的折扣计算流程,只是某些细节不同。这时候,就该提炼继承体系了。
我曾经接手过一个项目,里面有三个几乎一模一样的类,每个类有800多行代码。差异点只有十几个方法。当时我就在想:这要是改一个公共逻辑,得改三遍,而且很容易漏掉。
提炼继承体系的核心思路是:
- 找出公共部分——提取到父类或抽象类中
- 标记差异点——用模板方法模式或策略模式处理
- 子类只关注差异——每个子类只实现自己特有的行为
来看一个具体的例子:
// 提炼前:三个独立的类,大量重复代码
public class NormalOrderHandler {
public void process(Order order) {
validate(order);
calculateDiscount(order);
calculateShipping(order);
save(order);
}
// ... 几百行代码
}
public class VipOrderHandler {
public void process(Order order) {
validate(order);
calculateDiscount(order); // 和上面几乎一样
calculateShipping(order); // 和上面几乎一样
save(order);
sendVipGift(order); // 唯一的不同
}
// ... 几百行代码
}
提炼之后:
// 提炼后:父类封装公共逻辑
public abstract class OrderHandler {
public final void process(Order order) {
validate(order);
calculateDiscount(order);
calculateShipping(order);
save(order);
doExtra(order); // 模板方法,子类实现
}
protected void validate(Order order) { /* 公共校验 */ }
protected void calculateDiscount(Order order) { /* 公共折扣 */ }
protected void calculateShipping(Order order) { /* 公共运费 */ }
protected void save(Order order) { /* 公共保存 */ }
protected abstract void doExtra(Order order); // 子类扩展点
}
public class NormalOrderHandler extends OrderHandler {
@Override
protected void doExtra(Order order) {
// 普通订单不需要额外操作
}
}
public class VipOrderHandler extends OrderHandler {
@Override
protected void doExtra(Order order) {
sendVipGift(order);
}
}
注意:提炼继承体系不是「为了继承而继承」。如果两个类只有一两处不同,用策略模式或组合可能更合适。继承体系适合「行为骨架相同、细节可替换」的场景。
三、这两个动作如何配合?
我一般会先做分离领域与表现层,再做提炼继承体系。原因很简单:
- 领域逻辑抽离后,类的职责更清晰,更容易看出哪些类属于同一家族
- 表现层变薄之后,继承体系只存在于领域层,不会污染UI代码
- 测试更容易写——领域层的继承体系可以用纯单元测试覆盖
下面这张图展示了整个重构前后的结构变化:
四、避坑指南
这两个重构动作看着简单,实际操作中容易踩坑。我把自己踩过的坑分享给你:
我曾经犯过的错:
- 过度分离——把简单的页面也拆成领域层和表现层,结果代码量翻倍,维护成本反而高了。记住:分离是为了降低复杂度,不是为了炫技。
- 继承层次过深——提炼继承体系时,一口气搞了四五层继承。后来改一个底层方法,上层全受影响。我建议继承不超过三层,超过三层考虑用组合。
- 忽略表现层的「残留逻辑」——分离之后,表现层可能还藏着一些「小逻辑」,比如格式化日期、拼接字符串。这些虽然不算核心领域逻辑,但也要统一管理,否则又会散落一地。
我的个人习惯:每次重构只做一件事。要么分离领域与表现层,要么提炼继承体系。两件事混在一起做,容易搞乱。做完一个,跑一遍测试,确认没问题,再做下一个。
五、总结
分离领域与表现层,说白了就是让业务逻辑「回家」——回到它该待的领域层。提炼继承体系,是让相似的类「认祖归宗」——共享公共行为,只保留差异。
这两个动作做完,你的代码会变得:
- 更容易测试——领域逻辑不依赖UI,纯单元测试搞定
- 更容易扩展——加一个新类型的订单处理器,只需要写一个子类
- 更容易维护——改业务规则只改领域层,不会动到页面代码
嗯,大型重构就是这样,一步一个脚印。别想着一次搞定所有问题,每次解决一个痛点,系统就会慢慢变好。