20、大型重构(二):分离领域与表现层、提炼继承体系

大型重构做到这一步,其实已经进入深水区了。

上一章我们聊了如何拆分上帝类、梳理模块边界。今天要说的这两个动作——分离领域与表现层提炼继承体系——是真正让系统「脱胎换骨」的关键步骤。

我个人习惯把这两步放在一起做。为什么呢?因为领域逻辑一旦从表现层抽离出来,你会发现很多类之间天然存在「父子关系」或「兄弟关系」,这时候提炼继承体系就顺理成章了。

一、先聊聊「分离领域与表现层」

说白了,就是把业务逻辑和界面代码拆开。

我在项目中遇到过一种典型场景:一个订单管理页面,里面直接写了计算折扣、校验库存、生成物流单号的代码。页面一多,同样的逻辑散落在十几个地方。改一个折扣规则,得把所有页面翻一遍——这种痛,经历过的人都懂。

核心原则:表现层只负责「展示」和「交互」,不负责「决策」和「计算」。

具体怎么做?我建议分三步走:

  1. 识别「领域知识」——哪些代码是在描述业务规则?比如「满100减20」、「VIP用户免运费」。
  2. 提取领域服务——把这些逻辑封装成独立的类或方法,不依赖任何UI组件。
  3. 表现层只调用——页面里只保留事件绑定、数据绑定、状态切换,所有业务逻辑都委托给领域层。

举个例子,重构前可能是这样的:

// 重构前:表现层和领域逻辑混在一起
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代码
  • 测试更容易写——领域层的继承体系可以用纯单元测试覆盖

下面这张图展示了整个重构前后的结构变化:

重构前后结构对比 重构前:混乱耦合 表现层(含领域逻辑) 表现层(含领域逻辑) 表现层(含领域逻辑) 重构后:清晰分层 表现层(仅展示与交互) 领域层(业务逻辑) 父类:OrderHandler NormalOrderHandler VipOrderHandler EnterpriseHandler 数据层(持久化) 领域层内部通过继承体系组织,表现层只依赖领域层接口 每个层次职责单一,修改互不影响 ❌ 混乱 ✅ 清晰

四、避坑指南

这两个重构动作看着简单,实际操作中容易踩坑。我把自己踩过的坑分享给你:

我曾经犯过的错:

  • 过度分离——把简单的页面也拆成领域层和表现层,结果代码量翻倍,维护成本反而高了。记住:分离是为了降低复杂度,不是为了炫技。
  • 继承层次过深——提炼继承体系时,一口气搞了四五层继承。后来改一个底层方法,上层全受影响。我建议继承不超过三层,超过三层考虑用组合。
  • 忽略表现层的「残留逻辑」——分离之后,表现层可能还藏着一些「小逻辑」,比如格式化日期、拼接字符串。这些虽然不算核心领域逻辑,但也要统一管理,否则又会散落一地。

我的个人习惯:每次重构只做一件事。要么分离领域与表现层,要么提炼继承体系。两件事混在一起做,容易搞乱。做完一个,跑一遍测试,确认没问题,再做下一个。

五、总结

分离领域与表现层,说白了就是让业务逻辑「回家」——回到它该待的领域层。提炼继承体系,是让相似的类「认祖归宗」——共享公共行为,只保留差异。

这两个动作做完,你的代码会变得:

  • 更容易测试——领域逻辑不依赖UI,纯单元测试搞定
  • 更容易扩展——加一个新类型的订单处理器,只需要写一个子类
  • 更容易维护——改业务规则只改领域层,不会动到页面代码

嗯,大型重构就是这样,一步一个脚印。别想着一次搞定所有问题,每次解决一个痛点,系统就会慢慢变好。


公众号:蓝海资料掘金营,微信deep3321