zoukankan      html  css  js  c++  java
  • 设计模式简记-设计原则之迪米特法则

    3.8 迪米特法则

    3.8.1 何为高内聚、低耦合

    • 高内聚:

      相近的功能应该放到同一个类中,不相近的功能不要放到同一个类中。相近的功能往往会被同时修改,放到同一个类中,修改会比较集中,代码容易维护。

    • 低耦合:

      类与类之间的依赖关系简单清晰。即使两个类有依赖关系,一个类的代码改动不会或者很少导致依赖类的代码改动。

    3.8.2 何为迪米特法则(LOD)?

    • 迪米特法则的英文翻译是:Law of Demeter,缩写是 LOD
    • 另一个更加达意的名字,叫作最小知识原则,英文翻译为:The Least Knowledge Principle
    • 每个模块(unit)只应该了解那些与它关系密切的模块(units: only units “closely” related to the current unit)的有限知识(knowledge)。或者说,每个模块只和自己的朋友“说话”(talk),不和陌生人“说话”(talk)。
    • 不该有直接依赖关系的类之间,不要有依赖;有依赖关系的类之间,尽量只依赖必要的接口(也就是定义中的“有限知识”)。

    3.8.3 代码实战一:理解“不该有直接依赖关系的类之间,不要有依赖“

    • 简版搜索引擎爬取网页的功能。代码中包含三个主要的类。

      • NetworkTransporter 类负责底层网络通信,根据请求获取数据;
      • HtmlDownloader 类用来通过 URL 获取网页;
      • Document 表示网页文档,后续的网页内容抽取、分词、索引都是以此为处理对象。

      具体的代码实现如下所示:

    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。重构如下:

      public class NetworkTransporter {
          // 省略属性和其他方法...
          public Byte[] send(String address, Byte[] data) {
            //...
          }
      }
      
    • HtmlDownloader 类。类的设计没有问题。修改了 NetworkTransporter 的 send() 函数的定义,需要对它做相应的修改,修改后的代码如下所示:

      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);
        }
      }
      
    • Document 类。这个类的问题有三点:

      • 构造函数中的 downloader.downloadHtml() 逻辑复杂,耗时长,不应该放到构造函数中,会影响代码的可测试性。
      • HtmlDownloader 对象在构造函数中通过 new 来创建,违反了基于接口而非实现编程的设计思想,也会影响到代码的可测试性。
      • 从业务含义上来讲,Document 网页文档没必要依赖 HtmlDownloader 类,违背了迪米特法则。

      使用工厂方法解耦,重构如下:

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

    3.8.4 代码实战二:理解“有依赖关系的类之间,尽量只依赖必要的接口”

    • Serialization 类负责对象的序列化和反序列化:

      public class Serialization {
        public String serialize(Object object) {
          String serializedResult = ...;
          //...
          return serializedResult;
        }
        
        public Object deserialize(String str) {
          Object deserializedResult = ...;
          //...
          return deserializedResult;
        }
      }
      
      • 单看这个类的设计,没有一点问题。
      • 在特定的应用场景里,那就还有继续优化的空间。假设在项目中,有些类只用到了序列化操作,而另一些类只用到反序列化操作。那基于迪米特法则后半部分“有依赖关系的类之间,尽量只依赖必要的接口”,只用到序列化操作的那部分类不应该依赖反序列化接口。同理,只用到反序列化操作的那部分类不应该依赖序列化接口。重构如下:
      public class Serializer {
        public String serialize(Object object) {
          String serializedResult = ...;
          ...
          return serializedResult;
        }
      }
      
      public class Deserializer {
        public Object deserialize(String str) {
          Object deserializedResult = ...;
          ...
          return deserializedResult;
        }
      }
      
    • 新的问题:能满足迪米特法则,但却违背了高内聚的设计思想

    • 高内聚要求相近的功能要放到同一个类中,这样可以方便功能修改的时候,修改的地方不至于过于分散。

      • 如果修改了序列化的实现方式,比如从 JSON 换成了 XML,那反序列化的实现逻辑也需要一并修改。在未拆分的情况下,只需要修改一个类即可。在拆分之后,需要修改两个类。
    • 既不想违背高内聚的设计思想,也不想违背迪米特法则,如何做?

      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;
        }
        //...
      }
      

      以上代码,尽管还是要往 DemoClass_1 的构造函数中,传入包含序列化和反序列化的 Serialization 实现类,但是,我们依赖的 Serializable 接口只包含序列化操作,DemoClass_1 无法使用 Serialization 类中的反序列化接口,对反序列化操作无感知,这也就符合了迪米特法则后半部分所说的“依赖有限接口”的要求。

    3.8.5 总结

    设计原则 适用对象 侧重点 思考角度
    单一职责 模块、类、接口 高内聚、低耦合 自身
    接口隔离 接口、函数 低耦合 调用者
    基于接口而非实现编程 接口、抽象类 低耦合 调用者
    迪米特法则 模块、类 低耦合 类关系
  • 相关阅读:
    Jsp入门EL表达式_学习笔记
    sql-DDL, DML 常用语句
    sql-DDL, DML 常用语句
    sql-DDL, DML 常用语句
    sql-DDL, DML 常用语句
    谁需要GUI?快看Linux 终端生存之道
    谁需要GUI?快看Linux 终端生存之道
    谁需要GUI?快看Linux 终端生存之道
    谁需要GUI?快看Linux 终端生存之道
    2.3 根据层级查找元素
  • 原文地址:https://www.cnblogs.com/wod-Y/p/12770734.html
Copyright © 2011-2022 走看看