迪米特原则(设计默斯和)

定义

 高内聚低耦合是一个非常重要的设计思想,能够很好的提高代码的可读性和可维护性,缩小功能改动导致导致代码改动范围,实际上,在前面的章节中,我们已经多次提到了这个设计思想。很多设计原则都是以代码的高内聚低耦合为目的的,比如单一原则,基于接口而非编程实现

实际上,高内部松耦合,是一个比较通用的设计思路,可以用来指导不同粒度的设计于开发,比如系统,模块,类,甚至函数。也亦可以应用到不同的开发场景中,比如微服务,框架组件,类库等。为了方便讲解,我们以类作为这个设计思想的应用对象来展开讲解。其他的应用场景你可以自行类比。 

高内聚

它说的是讲相近的功能放到同一个类中,不相近的功能放在不同的类中,相近的功能往往被同时修改,代码的维护比较容易,实际上,我们前边讲解的单一原则,是高内聚非常有效的原则,

松耦合

所谓松耦合,在代码中,类鱼类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的改动不会后者很多好导致依赖类的代码的改动。实际上我们之前的依赖注入,基于接口而非实现,以及今天讲的迪米特原则都是为了实现代码的松耦合。 

内聚合耦合之间的关系

高内聚有助于实现松耦合,同理低耦合也会导致紧耦合。 关于这一点,下边的一张对比如来解释。

左边部分的代码的设计中,类的粒度比较小,每个类的职责比较单一,行进的功能都放在一个类中,不相近的功能放在了多个类中。这样类更加独立,代码的内聚性更高。 右边的代码的设计中,类的粒度比较大,低内聚,功能大而全,不相近的功能放到了一个类中。这就导致了很多其他类都依赖这个类。当我们修改这个类的某一个功能代码的时候,会影响依赖它的多个类。 我们需要测试这三个依赖类。是否正常工作。这就是所谓的牵一发而动全身。 

除此之外,从图中我们可以看出,高内聚低耦合的代码更加的简单,清晰,响应的在可维护和可读性上确实高了很多。 

迪米特原则: 

每个模块直应该了解那些于它关系密切的模块的有限知识。或者说,每个模块只能和自己的朋友说话,不要和陌生人说话。 

具体实战

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);
    }
}

  

现在,我们再来看以下这条原则的后半部分,有依赖关系的类之间,尽量只依赖必要的接口。 

 

posted @ 2024-02-28 14:24  dousil  阅读(52)  评论(0)    收藏  举报