ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

设计模式简记-设计原则之迪米特法则

2020-04-24 22:53:24  阅读:257  来源: 互联网

标签:... 序列化 String url 之迪 class 简记 设计模式 public


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 总结

设计原则 适用对象 侧重点 思考角度
单一职责 模块、类、接口 高内聚、低耦合 自身
接口隔离 接口、函数 低耦合 调用者
基于接口而非实现编程 接口、抽象类 低耦合 调用者
迪米特法则 模块、类 低耦合 类关系

标签:...,序列化,String,url,之迪,class,简记,设计模式,public
来源: https://www.cnblogs.com/wod-Y/p/12770734.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有