定义
高内聚低耦合是一个非常重要的设计思想,能够很好的提高代码的可读性和可维护性,缩小功能改动导致导致代码改动范围,实际上,在前面的章节中,我们已经多次提到了这个设计思想。很多设计原则都是以代码的高内聚低耦合为目的的,比如单一原则,基于接口而非编程实现
实际上,高内部松耦合,是一个比较通用的设计思路,可以用来指导不同粒度的设计于开发,比如系统,模块,类,甚至函数。也亦可以应用到不同的开发场景中,比如微服务,框架组件,类库等。为了方便讲解,我们以类作为这个设计思想的应用对象来展开讲解。其他的应用场景你可以自行类比。
高内聚
它说的是讲相近的功能放到同一个类中,不相近的功能放在不同的类中,相近的功能往往被同时修改,代码的维护比较容易,实际上,我们前边讲解的单一原则,是高内聚非常有效的原则,
松耦合
所谓松耦合,在代码中,类鱼类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的改动不会后者很多好导致依赖类的代码的改动。实际上我们之前的依赖注入,基于接口而非实现,以及今天讲的迪米特原则都是为了实现代码的松耦合。
内聚合耦合之间的关系
高内聚有助于实现松耦合,同理低耦合也会导致紧耦合。 关于这一点,下边的一张对比如来解释。
左边部分的代码的设计中,类的粒度比较小,每个类的职责比较单一,行进的功能都放在一个类中,不相近的功能放在了多个类中。这样类更加独立,代码的内聚性更高。 右边的代码的设计中,类的粒度比较大,低内聚,功能大而全,不相近的功能放到了一个类中。这就导致了很多其他类都依赖这个类。当我们修改这个类的某一个功能代码的时候,会影响依赖它的多个类。 我们需要测试这三个依赖类。是否正常工作。这就是所谓的牵一发而动全身。
除此之外,从图中我们可以看出,高内聚低耦合的代码更加的简单,清晰,响应的在可维护和可读性上确实高了很多。
迪米特原则:
每个模块直应该了解那些于它关系密切的模块的有限知识。或者说,每个模块只能和自己的朋友说话,不要和陌生人说话。
具体实战
public class NetworkTransporter {// 省略属性和其他方法...public Byte[] send(HtmlRequest htmlRequest) { //...} } public class HtmlDownloader {private NetworkTransporter transporter;// 通过构造函数或 IOC 注入public Html downloadHtml(String url) {Byte[] rawHtml = transporter.send(new HtmlRequest(url));return new Html(rawHtml);} } public class Document {private Html html;private String url;public Document(String url) {this.url = url;HtmlDownloader downloader = new HtmlDownloader();this.html = downloader.downloadHtml(url);} }
我们来看NetworkTransporter类,作为一个底层网络通信类,我们希望它尽可能的通用,而不知服务于下载html ,所以我们不应该直接依赖太具体的发送对象HtmlRequest。从这一点上讲,NetworkTransporter类的设计违背了迪米特原则,依赖不该直接依赖的关系的HtmlRequest类。
我们应该如何重构,让NetworkTransporter 类满足迪米特法则呢? 我这里有一个形象的比喻,加入你线程需要去商店买东西,你看到你定不会直接把浅包给收银员。让收银员从你的钱包力拿钱。而是你从钱包力把钱拿出来交给收银员。 这里的HtmlRequest 就相当于钱包,HtmlRequest 里边的address 和context 对象就相当于钱。我们应该把address 和context 交割NetworkTransporter ,而不死把HtmlRequest 交给NetworkTransporter 。根据这个思路。NetworkTransporter 重构之后的代码如下:
public class NetworkTransporter {// 省略属性和其他方法...public Byte[] send(String address, Byte[] data) {//...} }
最后我们来看Document 类。这个类。主要有三点,1.构造函数的downloader.downloadHtml() 逻辑复杂,耗时长,不应该放到构造函数中,会影响代码的可测试性。HtmlDownloader对象在构造函数中通过new 来创建的,违反了基于接口而非编程的设计思想。 也会影响代码的可测试性。从业务上,Document 网页文档没必要依赖HtmDownloader 类,违背了迪米特原则
public class Document {private Html html;private String url;public Document(String url, Html html) {this.html = html;this.url = url;} //... } // 通过一个工厂方法来创建 Document public class DocumentFactory {private HtmlDownloader downloader;public DocumentFactory(HtmlDownloader downloader) {this.downloader = downloader;}public Document createDocument(String url) {Html html = downloader.downloadHtml(url);return new Document(url, html);} }
现在,我们再来看以下这条原则的后半部分,有依赖关系的类之间,尽量只依赖必要的接口。