内聚(Cohesion)

定义:一个模块内部各元素之间关联的紧密程度。

高内聚:模块内的代码服务于同一个明确的职责。

低内聚的表现:

  • 一个 Utils 类里塞了字符串处理、日期计算、HTTP 请求、加密……彼此毫无关系;
  • 一个函数既做数据校验、又做业务计算、还负责写数据库和发通知。

耦合(Coupling)

定义:模块之间的依赖程度。

低耦合:模块之间的依赖少而清晰,一个模块的变化不会波及很多其它模块。

高耦合的表现:

  • 改一个类,要改十几个引用它的地方;
  • 模块之间互相引用(循环依赖);
  • 直接依赖具体实现,而非抽象接口。

为什么要这样

低内聚的问题:一个类做太多事,你就很难理解它、修改它、测试它。任何一项职责的变化都可能影响到其他职责。

高耦合的问题:修改会"传染"。一个小的改动引发连锁反应,回归风险大,且难以单独复用某个模块。

两者往往相关:内聚度低的代码,通常耦合也高——因为它把不相关的东西搅在一起,必然与更多外部代码产生联系。

怎么判断内聚

一个简单的检验:你能用一句话说清楚这个模块是做什么的吗?

如果能,且这句话中不包含"和""以及""还有",那内聚度通常不错。

如果需要"这个文件负责 A、B,还有 C 的一些事情"——那它可能需要拆分。

怎么降低耦合

一、依赖抽象,而非具体

面向接口编程。调用方依赖接口,实现可以替换。

二、依赖注入(DI)

不要在类内部 new 一个依赖,而是从外部传入。

这让依赖可见(看构造函数就知道依赖什么),也便于测试时替换。

三、单一职责原则(SRP)

一个类应该只有一个引起它变化的原因。

四、接口隔离原则(ISP)

不要强迫调用方依赖它不需要的方法。与其一个"胖接口",不如几个职责明确的小接口。

五、迪米特法则(最少知识原则)

一个对象应该尽可能少地了解其他对象。

a.getB().getC().doSomething() 这样的链式调用,意味着 a 的调用方了解了 B 和 C 的内部结构——耦合过高。

六、事件/消息机制

用事件解耦:A 不需要知道 B 的存在,只需发布一个事件,由 B 自行订阅。

一个平衡

过度的低耦合也有代价:

  • 过多的抽象层和接口,会让代码难以追踪("这段代码到底在哪里实现?");
  • 为了解耦而解耦,可能引入不必要的复杂度。

判断标准:这个抽象是否真的会被替换?如果永远只有一个实现,那么接口可能是多余的(除非为了测试隔离,这是个合理理由)。

一句话

高内聚、低耦合的目标,是让变化局部化——当需求变化时,你只需要改动少数几个地方,而不是牵一发动全身。

这是软件可维护性的核心。

说明:本文为软件设计原则的知识整理。