设计模式之美
王争
前Google工程师,《数据结构与算法之美》专栏作者
立即订阅
18135 人已学习
课程目录
已更新 25 讲 / 共 100 讲
0/6登录后,你可以任选6讲全文学习。
开篇词 (1讲)
开篇词 | 一对一的设计与编码集训,让你告别没有成长的烂代码!
免费
设计模式学习导读 (3讲)
01 | 为什么说每个程序员都要尽早地学习并掌握设计模式相关知识?
02 | 从哪些维度评判代码质量的好坏?如何具备写出高质量代码的能力?
03 | 面向对象、设计原则、设计模式、编程规范、重构,这五者有何关系?
设计原则与思想:面向对象 (11讲)
04 | 理论一:当谈论面向对象的时候,我们到底在谈论什么?
05 | 理论二:封装、抽象、继承、多态分别可以解决哪些编程问题?
06 | 理论三:面向对象相比面向过程有哪些优势?面向过程真的过时了吗?
07 | 理论四:哪些代码设计看似是面向对象,实际是面向过程的?
08 | 理论五:接口vs抽象类的区别?如何用普通的类模拟抽象类和接口?
09 | 理论六:为什么基于接口而非实现编程?有必要为每个类都定义接口吗?
10 | 理论七:为何说要多用组合少用继承?如何决定该用组合还是继承?
11 | 实战一(上):业务开发常用的基于贫血模型的MVC架构违背OOP吗?
12 | 实战一(下):如何利用基于充血模型的DDD开发一个虚拟钱包系统?
13 | 实战二(上):如何对接口鉴权这样一个功能开发做面向对象分析?
14 | 实战二(下):如何利用面向对象设计和编程开发接口鉴权功能?
设计原则与思想:设计原则 (8讲)
15 | 理论一:对于单一职责原则,如何判定某个类的职责是否够“单一”?
16 | 理论二:如何做到“对扩展开放、修改关闭”?扩展和修改各指什么?
17 | 理论三:里式替换(LSP)跟多态有何区别?哪些代码违背了LSP?
18 | 理论四:接口隔离原则有哪三种应用?原则中的“接口”该如何理解?
19 | 理论五:控制反转、依赖反转、依赖注入,这三者有何区别和联系?
20 | 理论六:我为何说KISS、YAGNI原则看似简单,却经常被用错?
21 | 理论七:重复的代码就一定违背DRY吗?如何提高代码的复用性?
22 | 理论八:如何用迪米特法则(LOD)实现“高内聚、松耦合”?
不定期加餐 (2讲)
加餐一 | 用一篇文章带你了解专栏中用到的所有Java语法
加餐二 | 设计模式、重构、编程规范等相关书籍推荐
设计模式之美
登录|注册

22 | 理论八:如何用迪米特法则(LOD)实现“高内聚、松耦合”?

王争 2019-12-23
今天,我们讲最后一个设计原则:迪米特法则。尽管它不像 SOLID、KISS、DRY 原则那样,人尽皆知,但它却非常实用。利用这个原则,能够帮我们实现代码的“高内聚、松耦合”。今天,我们就围绕下面几个问题,并结合两个代码实战案例,来深入地学习这个法则。
什么是“高内聚、松耦合”?
如何利用迪米特法则来实现“高内聚、松耦合”?
有哪些代码设计是明显违背迪米特法则的?对此又该如何重构?
话不多说,让我们开始今天的学习吧!

何为“高内聚、松耦合”?

“高内聚、松耦合”是一个非常重要的设计思想,能够有效地提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围。实际上,在前面的章节中,我们已经多次提到过这个设计思想。很多设计原则都以实现代码的“高内聚、松耦合”为目的,比如单一职责原则、基于接口而非实现编程等。
实际上,“高内聚、松耦合”是一个比较通用的设计思想,可以用来指导不同粒度代码的设计与开发,比如系统、模块、类,甚至是函数,也可以应用到不同的开发场景中,比如微服务、框架、组件、类库等。为了方便我讲解,接下来我以“类”作为这个设计思想的应用对象来展开讲解,其他应用场景你可以自行类比。
在这个设计思想中,“高内聚”用来指导类本身的设计,“松耦合”用来指导类与类之间依赖关系的设计。不过,这两者并非完全独立不相干。高内聚有助于松耦合,松耦合又需要高内聚的支持。
取消
完成
0/1000字
划线
笔记
复制
© 版权归极客邦科技所有,未经许可不得传播售卖。 页面已增加防盗追踪,如有侵权极客邦将依法追究其法律责任。
该试读文章来自付费专栏《设计模式之美》,如需阅读全部文章,
请订阅文章所属专栏。
立即订阅
登录 后留言

精选留言(29)

  • 知行合一
    目的都是实现高内聚低耦合,但是出发的角度不一样,单一职责是从自身提供的功能出发,迪米特法则是从关系出发,针对接口而非实现编程是使用者的角度,殊途同归。
    2019-12-23
    28
  • Ken张云忠
    “高内聚、松耦合”“单一职责原则”“接口隔离原则”“基于接口而非实现编程”“迪米特法则”,它们之间的区别和联系吗?
    区别:
    高内聚、松耦合:是一个重要的设计思想,能够有效地提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围.
    单一职责原则:A class or module should have a single reponsibility.提供的功能上要单一.
    接口隔离原则:Clients should not be forced to depend upon interfaces that they do not use.与外部关系上只依赖需要的抽象.
    基于接口而非实现编程:Program to an interface, not an implementation.是一条比较抽象、泛化的设计思想,为了提高代码的灵活性/扩展性/可维护性.
    迪米特法则:Each unit should have only limited knowledge about other units: only units “closely” related to the current unit. Or: Each unit should only talk to its friends; Don’t talk to strangers.每个单元只该依赖与它关系密切的单元,最少知道,只与关系密切的单一交互.
    联系:
    职责越单一越容易做到接口隔离,也越容易做到最少知道的迪米特法则.
    基于抽象编程抽象的知识越顶层越脱离具体实现,相对知道的内容就越少,也容易实现迪米特法则.
    接口隔离原则与迪米特法则都强调只依赖需要的部分,接口隔离原则是相对偏上层来说的,迪米特法则是相对偏具体实现来说的.
    单一职责原则/接口隔离原则/基于接口而非实现编程/迪米特法则都以实现代码的"高内聚、松耦合"为目的,提高代码的可读性和可维护性,缩小功能改动导致的代码改动范围,降低风险.
    2019-12-23
    9
  • 辣么大
    关于LoD,请记住一条:方法中不要使用ChainMethods。

    坏的实践:
    Amount = customer.orders().last().totals().amount()

    orders = customer.orders()
    lastOders = orders.last()
    totals = lastOders.totals()
    amount = totals.amount()

    上面的例子中,chain中的方法改变会影响很多地方。这里注意区别建造者模式和pipeline管道,这两种的chain中的方法不易改变。

    出现这样的代码,需要考虑可能是设计或实现出了问题。

    LoD如何使用:
    一个类C中的方法只能调用:
    1、C中其他实例方法
    2、它自己的参数方法
    3、它创建对象的方法
    4、不要调用全局变量(包括可变对象、可变单例)
    例如:
    class HtmlDownloader{
      Html html;
      public void downloadHtml(Transporter trans, String url){
        if(checkUrl(url)){// ok 自己的实例方法
          // return
        }
        rawData = trans.send(uri);// ok 参数对象的方法
        Html html = createHtml(rawData); // ok 它创建的对象
        html.save();// ok  它创建对象的方法
      )
      private boolean checkUrl(String url){
        // check
      }
    }

    参考:
    The Pragmatic Programmer 1st edition and 2nd edition
    2019-12-23
    5
    6
  • 黄林晴
    打卡
    所有的设计原则都相辅相成
    2019-12-23
    2
  • 刘浒
    “高内聚、松耦合” 是衡量好代码的标准之一,为了实现这样的目标,我们需要遵循如下原则:
    “基于接口而非实现编程”,接口本身就是一层抽象,接口是稳定的,实现是易变的,强调的是基于契约编程,这样能够隔离变化。实现细节代码的变化,不影依赖该接口的对象,到从而达到松耦合的目的。
    “迪米特法则”,定义的是发布的接口(类、模块等)能不能依赖,如何依赖的问题。使用者去除不必要的依赖,只依赖必要的接口。这样即使接口一旦发生变化,需要了解这一变化的类就会比较少,达到松耦合的目的。
    “接口隔离原则”,从使用者的角度考虑如何设计接口,让使用者只依赖必要的接口,不会被迫依赖不用的接口。这样即使接口一旦发生变化,需要了解这一变化的类就会比较少,这样就能符合 “迪米特法则” 。
    “单一职责原则”,针对模块、类、接口的设计,将功能相关性很强的代码抽取到一起,达到高内聚的目标。
    2019-12-23
    1
  • 落叶飞逝的恋
    课后讨论:代码的最终目的是高内聚、松耦合的。而为了达到这个目的,就需要利用到迪米特法则。而迪米特法则的实现,又需要利用单一职责将单个类定义职责单一化,并且为了解决多个类之间的关系,又需要用到基于接口编程而非实现编程。这样类与类之间就相当于契约化,也就是不关心类的具体实现。
    2019-12-23
    1
  • 逆风星痕
    感觉所有设计原则和模式,都是为了代码的可读性,复用和扩展而总结出来的。好多原则可能是针对某个场景下提高代码的复用和扩展,这样有时也会辅助其他原则。迪米特原则描述类之间的关系,尽量减少依赖,但也需要类遵循单一职责原则。设计代码的时候,可以根据自己的目的,从参考相应原则的设计
    2019-12-23
    1
  • 再见孙悟空
    “单一职责原则”“接口隔离原则”“基于接口而非实现编程”“迪米特法则”都是为了实现 “高内聚、低耦合”的手段。做到了接口隔离,一般情况下职责也比较单一,基于接口而非实现编程,往往也会降低耦合性。有的时候使用了迪米特法则或者单一职责原则,可能会破坏高内聚原则,这种情况就要具体分析场景,以及使用接口来实现。
    2019-12-23
    1
  • 失火的夏天
    接口隔离感觉就是为了迪米特法则的应用,接口隔离开不需要依赖的类,只引入需要的接口和方法。

    高内聚低耦合,是针对具体的实现类的,实现类实现多个接口,相似的功能都在同一个实现类中完成。

    接口的隔离又保证对外只暴露了调用方需要的方法,外部也不能直接看到不需要的方法。代码结构也更加整洁,逻辑更清晰
    2019-12-23
    1
  • 小毅
    对于第一个例子我个人觉得将NetworkTransporter 类的send方法对应的HtmlRequest中属性拉平,还不如调整更为贴切的名字~ 对于公共方法过多的参数其实不太好维护~
    2019-12-24
  • 下雨天
    适用对象 侧重点 思考角度
    单一职责原则 模块,类,接口 高内聚,低耦合 自身
    接口隔离原则 接口,函数 低耦合 调用者
    基于接口而非实现编程 接口,抽象类 低耦合 调用者
    迪米特法则 模块,类 低耦合 类关系
    2019-12-24
  • QQ怪
    老师讲的真棒👍
    2019-12-24
  • Frank
    个人觉得,单一职责原则、接口隔离原则、基于接口而非实现编程、迪米特法则都是实现高内聚和低耦合在不同角度的更加落地的思考。高内聚、低耦合是一个顶层目标,而这些原则是这个目标的不同角度的思考,比如接口隔离原则,基于接口而非实现编程是从接口的角度来思考。单一职责原则是从模块、类的角度来思考。而迪米特法则强调units之间不该有的关系就不要有,最好关系是清清楚楚,明明白白的。
    2019-12-23
  • DullBird
    同: 都是为了保证代码的稳定性,在变化的情况下,影响最小,不过各自纬度可能不同。
    单一职责原则: 基于功能点,要结合业务来考虑是否职责是否单一
    基于接口而非实现编程: 是基于变化的点,为了应对变化
    “接口隔离原则”和“迪米特法则”: 没太区分开。我目前理解是一样的,在于对调用方的关系上面,做到无关的不用理解。
    “高内聚、松耦合”: 是总的原则,类似于纲领,这个没太理解,总是挂嘴上,但是感觉有点模糊?

    提问:
    类似与序列化类的问题,代码的重构是渐进的。比如一开始,
    class A{
        //序列化
        a();
        //反序列化
        b();
    }
    当有一天,有3个序列化和3个反序列化方法的时候,准备拆分了,就像文中一样,实现两个接口,代码重构一下即可以让每个调用方知道最少,符合迪米特法则,应用内部修改确实没什么问题。但是比如dubbo服务,如果一开始没拆开,后面想拆开。又要考虑向前兼容的问题,目前的做法就是class A 实现2个接口,新增6个方法,有2个和原来的序列化反序列化一样,标记旧的过期。通常还有什么更好的办法么?
    2019-12-23
  • blacknhole
    对耦合的含义或理解方式有些不同看法:

    1,与其从依赖关系来谈松耦合,说它是依赖关系简单清晰,不如从功能的相关性来谈,说它是无关功能不放在一个类中。这时,高内聚的含义就只是相关或相近功能放在一个类中了。

    类之间的依赖关系,在类的设计完成之后,是无法选择和改变的,除非重新设计类。比如,类之间应该依赖时,在不改变类的设计的前提下,无法刻意不依赖或减少依赖。依赖关系简单清晰,只是在对高内聚和松耦合的适度追求之下,设计并实现类之后,自然产生的一个结果。

    2,高内聚和松耦合,都是用来指导类本身的设计的。并非用松耦合来指导类之间依赖关系的设计。换句话说,类之间依赖关系的设计应该包含在类本身的设计之中(事实上,只有类的设计,而不存在什么类之间依赖关系的设计,见下文)。

    在设计一个类时,应该考虑它与其他类的关系,以达到这个类的适度内聚和耦合。在这里,内聚是从功能相关的角度来观察类——有关的功能是不是放在一起了,耦合是从功能无关的相反角度来观察类——无关的功能是不是分散开来了。

    依赖关系是类的设计完成之后,对类之间相关性的描述,有关就叫有依赖关系,无关就叫无依赖关系。所以,并不存在什么对依赖关系的设计,依赖关系是类的设计完成之后的一个自然结果。

    3,这样的理解,对类的设计来说才是更有指导意义的,也才能使概念的边界足够清晰,从而使内聚与耦合的本质更易被准确理解。
    2019-12-23
  • Kang
    打卡
    2019-12-23
  • 岁月
    如果说高内聚、松耦合等价于"中国特色社会主义", 那么“单一职责原则”“接口隔离原则”“基于接口而非实现编程”“迪米特法则”这几个原则就像是在说如何才能做到做到这样的社会? 答案就是我们要"倡导富强、民主、文明、和谐, 自由、平等、公正、法治,爱国、敬业、诚信、友善"
    2019-12-23
  • Yes
    我认为所有的设计原则和设计模式都是为了达到高内聚、松耦合的目的,而设计原则是指导思想,设计模式是前人根据设计原则再结合常见痛点得出的具体落地方案。

    按照指导思想设计出来的代码就是会符合高内聚、松耦合。

    指导思想之间也是相辅相成的。

    单一职责思考的角度是功能方面,一个类一个模块职责单一,而迪米特法则考虑的是最小化依赖原则,也就是在职责单一的情况下再深挖,尽可能的减少类之间的依赖关系,以达到松耦合的目的。

    并且在这个过程中我们肯定选择的是基于接口而非实现编程,依赖抽象而不是依赖实现。在这种情况下再考虑最小化依赖原则,我们肯定是想剥除接口里面一些此场景下不需要的方法,因此接口隔离原则就运用上了。

    也就是说平日我们开发中需要时刻的想着这几种原则,它们分别从不同的角度来指导我们。但是盲目的运用是没必要的,一个再也简单两三个方法纷纷运用上这些原则,就过犹不及了,要想着KISS。

    代码是持续改进、不断重构的。
    2019-12-23
  • 守拙
    课堂讨论:

    在今天的讲解中,我们提到了“高内聚、松耦合”“单一职责原则”“接口隔离原则”“基于接口而非实现编程”“迪米特法则”,你能总结一下它们之间的区别和联系吗?



    Answer:

    1.高内聚与单一职责原则:

    ​ 高内聚形容类聚焦于本身的职责,是单一职责原则的具体体现。换句话说,遵守了单一职责原则而设计出的类,具有高内聚的特性。



    2.松耦合与接口隔离原则:

    ​ 低耦合形容类尽可能少的依赖其他类(接口),是接口隔离原则的具体体现。如果遵守接口隔离原则,就能设计出松耦合的类。



    3.基于接口而非实现编程与迪米特法则

    ​ 基于接口而非实现编程是一种面向对象设计思想,通过接口依赖而非具体类依赖的方式达到类之间松耦合的目的。

    ​ 此思想一定意义上沉淀为迪米特法则:解耦类之间的依赖关系,能不依赖的,就不要依赖;必须依赖的,要依赖接口,而不依赖实现。总而言之:基于接口而非实现编程是形而上的飘渺思想,迪米特法则是萃取思想精华的戒律清规。在每日的修行中:遵守戒律,参悟思想,才能有所精进。
    2019-12-23
  • whistleman
    打卡~
    1.不该有直接依赖关系的类之间,不要有依赖;
    2.有依赖关系的类之间,尽量只依赖必要的接口。
    2019-12-23
收起评论
29
返回
顶部