内聚(Cohesion)
定义:一个模块内部各元素之间关联的紧密程度。
高内聚:模块内的代码服务于同一个明确的职责。
低内聚的表现:
- 一个
Utils类里塞了字符串处理、日期计算、HTTP 请求、加密……彼此毫无关系; - 一个函数既做数据校验、又做业务计算、还负责写数据库和发通知。
耦合(Coupling)
定义:模块之间的依赖程度。
低耦合:模块之间的依赖少而清晰,一个模块的变化不会波及很多其它模块。
高耦合的表现:
- 改一个类,要改十几个引用它的地方;
- 模块之间互相引用(循环依赖);
- 直接依赖具体实现,而非抽象接口。
为什么要这样
低内聚的问题:一个类做太多事,你就很难理解它、修改它、测试它。任何一项职责的变化都可能影响到其他职责。
高耦合的问题:修改会"传染"。一个小的改动引发连锁反应,回归风险大,且难以单独复用某个模块。
两者往往相关:内聚度低的代码,通常耦合也高——因为它把不相关的东西搅在一起,必然与更多外部代码产生联系。
怎么判断内聚
一个简单的检验:你能用一句话说清楚这个模块是做什么的吗?
如果能,且这句话中不包含"和""以及""还有",那内聚度通常不错。
如果需要"这个文件负责 A、B,还有 C 的一些事情"——那它可能需要拆分。
怎么降低耦合
一、依赖抽象,而非具体
面向接口编程。调用方依赖接口,实现可以替换。
二、依赖注入(DI)
不要在类内部 new 一个依赖,而是从外部传入。
这让依赖可见(看构造函数就知道依赖什么),也便于测试时替换。
三、单一职责原则(SRP)
一个类应该只有一个引起它变化的原因。
四、接口隔离原则(ISP)
不要强迫调用方依赖它不需要的方法。与其一个"胖接口",不如几个职责明确的小接口。
五、迪米特法则(最少知识原则)
一个对象应该尽可能少地了解其他对象。
a.getB().getC().doSomething() 这样的链式调用,意味着 a 的调用方了解了 B 和 C 的内部结构——耦合过高。
六、事件/消息机制
用事件解耦:A 不需要知道 B 的存在,只需发布一个事件,由 B 自行订阅。
一个平衡
过度的低耦合也有代价:
- 过多的抽象层和接口,会让代码难以追踪("这段代码到底在哪里实现?");
- 为了解耦而解耦,可能引入不必要的复杂度。
判断标准:这个抽象是否真的会被替换?如果永远只有一个实现,那么接口可能是多余的(除非为了测试隔离,这是个合理理由)。
一句话
高内聚、低耦合的目标,是让变化局部化——当需求变化时,你只需要改动少数几个地方,而不是牵一发动全身。
这是软件可维护性的核心。
说明:本文为软件设计原则的知识整理。