模块化设计思想与复用 - 第2节

模块化设计原则:高内聚低耦合

说到模块化设计,我第一个想聊的就是「高内聚低耦合」。这六个字,说白了就是模块化设计的灵魂。我在刚入行那会儿,总觉得代码能跑就行,管它什么内聚耦合。结果呢?项目一迭代,改一处代码,崩三处功能。嗯,那种滋味,谁经历谁知道。

什么是高内聚?

高内聚,就是一个模块内部的东西,应该紧密相关。比如一个订单模块,它只管订单的创建、查询、修改。别把用户登录的逻辑也塞进去。

我个人的习惯是:一个模块只做一类事情。如果发现一个模块里既有订单逻辑,又有支付逻辑,还有物流逻辑,那就要警惕了。这通常意味着内聚度太低。

高内聚的好处:

  • 模块内部逻辑清晰,容易理解
  • 修改时影响范围小
  • 测试起来也方便,不用mock一堆外部依赖

什么是低耦合?

低耦合,就是模块之间的依赖关系要尽量弱。A模块改了,B模块最好不用动。你想想看,如果两个模块像连体婴儿一样,改一个就得改另一个,那还叫什么模块化?

我曾经在一个老项目里见过这样的代码:订单模块直接调用了用户模块的数据库表。后来用户表结构一改,订单模块全崩了。这就是典型的耦合过紧。

判断耦合度的简单方法:

问自己一个问题——如果我要把这个模块单独拿出来放到另一个项目里用,需要改多少代码?改得越少,耦合越低。

接口隔离原则

接口隔离原则,我理解起来就一句话:别让调用方依赖它不需要的东西。

举个例子,你设计了一个打印机接口,里面有打印、扫描、传真三个方法。但有的调用方只需要打印功能,却被迫依赖了扫描和传真。这就不合理。

// 不好的设计
public interface Printer {
    void print();
    void scan();
    void fax();
}

// 好的设计
public interface Printer {
    void print();
}

public interface Scanner {
    void scan();
}

public interface FaxMachine {
    void fax();
}

我在项目中遇到过类似的情况。一个支付模块的接口,里面定义了支付宝支付、支付、银行卡支付三个方法。结果有个新需求只需要接入支付,却不得不实现另外两个空方法。嗯,这就是接口隔离没做好。

注意:接口隔离不是越细越好。太细的接口会导致接口数量爆炸,反而增加维护成本。找到那个「刚好够用」的粒度,才是关键。

单一职责原则

单一职责原则,说白了就是一个模块只负责一件事情。听起来简单,做起来难。

我见过太多这样的代码:一个类既负责数据校验,又负责业务逻辑,还负责持久化。这种类,我称之为「万能类」。改起来心惊胆战,测起来无从下手。

判断是否违反单一职责:

  • 这个模块的修改原因是否多于一个?
  • 如果需求变了,这个模块是否需要改?
  • 这个模块的名字是否很难起?

举个例子,一个用户注册模块。如果它既负责校验用户输入,又负责发送注册邮件,还负责记录日志,那它就违反了单一职责。正确的做法是拆成三个模块:用户校验模块、邮件发送模块、日志记录模块。

我的经验:写代码前先花5分钟想想这个模块的职责边界。想清楚了再动手,往往能省下后面大量的重构时间。

依赖倒置原则

依赖倒置原则,核心思想是:高层模块不应该依赖低层模块,两者都应该依赖抽象。抽象不应该依赖细节,细节应该依赖抽象。

听起来有点绕?我换个说法:别让业务逻辑直接依赖具体实现,而是依赖接口或抽象类。

// 违反依赖倒置
public class OrderService {
    private MySQLOrderRepository repository = new MySQLOrderRepository();
    // 直接依赖了具体实现
}

// 符合依赖倒置
public class OrderService {
    private OrderRepository repository;
    
    public OrderService(OrderRepository repository) {
        this.repository = repository;
    }
    // 依赖抽象接口
}

我曾经在一个项目中,所有业务模块都直接依赖了某个第三方支付SDK。后来这个SDK升级了,接口大变,我们不得不改了几十个文件。如果当时依赖倒置做得好,只需要改一个适配层就够了。

避坑指南:依赖倒置不是让你把所有东西都抽象化。过度抽象同样可怕。我见过一个项目,连一个简单的工具类都要抽象出接口来,结果代码量翻了三倍,可读性反而下降了。

四大原则的关系

这四个原则不是孤立的,它们相辅相成。高内聚低耦合是目标,单一职责是手段,接口隔离是保障,依赖倒置是方法。

我画了一张图,帮你理清它们的关系:

高内聚低耦合 核心目标 单一职责原则 一个模块只做一件事 接口隔离原则 不依赖不需要的东西 依赖倒置原则 依赖抽象而非具体 四大原则共同支撑模块化设计 单一职责 → 高内聚 接口隔离 + 依赖倒置 → 低耦合

总结一下:

  • 高内聚低耦合:模块内部紧密,模块之间松散
  • 单一职责:一个模块只做一件事,做好一件事
  • 接口隔离:接口要小而精,别让调用方背负不必要的依赖
  • 依赖倒置:面向接口编程,别面向具体实现编程

这四个原则,说起来容易做起来难。我做了这么多年架构设计,也不敢说每次都能完美应用。但有一点我可以肯定:遵循这些原则的项目,后期维护成本会低很多。你想想看,一个模块化设计良好的系统,改起来就像搭积木一样,拆一块换一块,不影响其他部分。这种感觉,真的很爽。

最后分享一个小技巧:每次写完代码,回头看看能不能用这四个原则来审视一下。如果发现某个地方违反了,别急着改,先想想是不是真的有必要。有时候,适度的「违规」反而能让代码更简洁。原则是死的,人是活的。