LOD原则

迪米特法则(Law of Demeter):又称最小知识原则。不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口。

利用这个原则,能够帮我们实现代码的 高内聚、松耦合

高内聚、松耦合

很多设计原则都以实现代码的 高内聚、松耦合 为目的,比如单一职责原则、基于接口而非实现编程等。

高内聚

所谓高内聚,就是指相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。

松耦合

所谓松耦合是说,在代码中,类与类之间的依赖关系简单清晰。
即使两个类有依赖关系,一个类的代码改动不会或者很少导致依赖类的代码改动。

两者之间的关系

高内聚、松耦合 是一个比较通用的设计思想,可以用来指导不同粒度代码的设计与开发,
比如系统、模块、类,甚至是函数,也可以应用到不同的开发场景中,比如微服务、框架、组件、类库等。

不该有直接依赖关系的类之间,不要有依赖

例:简化版的搜索引擎爬取网页功能

// 负责底层网络通信,根据请求获取数据
public class NetworkTransporter {
    // 省略属性和其他方法...
    public Byte[] send(HtmlRequest htmlRequest) {
      //...
    }
}

// 用来通过 URL 获取网页
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);
  }
  //...
}

重构后的代码:


public class NetworkTransporter {
    // 省略属性和其他方法...
    public Byte[] send(String address, Byte[] data) {
      //...
    }
}


public class HtmlDownloader {
  private NetworkTransporter transporter;//通过构造函数或IOC注入
  
  // HtmlDownloader这里也要有相应的修改
  public Html downloadHtml(String url) {
    HtmlRequest htmlRequest = new HtmlRequest(url);
    Byte[] rawHtml = transporter.send(
      htmlRequest.getAddress(), htmlRequest.getContent().getBytes());
    return new Html(rawHtml);
  }
}


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

有依赖关系的类之间,尽量只依赖必要的接口

单一职责中序列化与反序列化 的例子中,合并之后的类,在某些场景下违法了迪米特法则。
假设在我们的项目中,有些类只用到了序列化操作,而另一些类只用到反序列化操作。
那基于迪米特法则后半部分 有依赖关系的类之间,尽量只依赖必要的接口,只用到序列化操作的那部分类不应该依赖反序列化接口。
同理,只用到反序列化操作的那部分类不应该依赖序列化接口。

尽管拆分之后的代码更能满足迪米特法则,但却违背了高内聚的设计思想。
高内聚要求相近的功能要放到同一个类中,这样可以方便功能修改的时候,修改的地方不至于过于分散。

实际上,通过引入两个接口就能同时满足迪米特法则和高内聚设计思想:


public interface Serializable {
  String serialize(Object object);
}

public interface Deserializable {
  Object deserialize(String text);
}

public class Serialization implements Serializable, Deserializable {
  @Override
  public String serialize(Object object) {
    String serializedResult = ...;
    ...
    return serializedResult;
  }
  
  @Override
  public Object deserialize(String str) {
    Object deserializedResult = ...;
    ...
    return deserializedResult;
  }
}

public class DemoClass_1 {
  private Serializable serializer;
  
  public Demo(Serializable serializer) {
    this.serializer = serializer;
  }
  //...
}

public class DemoClass_2 {
  private Deserializable deserializer;
  
  public Demo(Deserializable deserializer) {
    this.deserializer = deserializer;
  }
  //...
}

上面的的代码实现思路,也体现了 基于接口而非实现编程 的设计原则,结合迪米特法则,可以总结出一条新的设计原则,那就是 基于最小接口而非最大实现编程