吐槽 依赖倒置原则/DIP

返回目录   1.2.5抛弃依赖倒置原则(正文) ,本文专门吐槽依赖倒置原则(Dependency Inversion Principle、DIP)。

1.Robert C. Martin的那篇DIP文章(于1996在一个专栏上发表),可以看成他学习《设计模式》的学习笔记,就像CSDN上大量的博客一样。这种东西,有再多的问题都是正常的。我的博客也一样,有时候想到什么说什么,管它逻辑不逻辑。所以,他的论文有错误,说明他当时没有学习/研究清楚。他希望将针对接口编程控制反转纳入一个原则即DIP中,因此形成了一个思路混乱的、不知所云的原则。

2.或许DIP最早的翻译文章就是我(2005年)帖出来的。该文章含糊晦涩,读起来有些烦人,而且当时没有太多人提到DIP,所以将它作为“针对接口编程”的晦涩版。后来,该原则被收入到Robert C. Martin的著作《敏捷软件开发》中,国内外有大量文章和书籍——国内几乎所有关于设计模式的书籍都提到它,那么DIP文章就要好好读一下了。

一读不打紧,错误太多,我都不知道该怎样批驳他了。或者说,该文能够搞出这么多错误,我都有点佩服他了。有逻辑混乱的,有似是而非的,有胡说八道的,这么胡言乱语的神经病的东西,要逐一地指出其问题,差一点 把我都搞得神经病了。毕竟,指出胡说八道和逻辑问题,比较简单;而一大堆的似是而非的用词和说明搅合在一起,可能没有人愿意花时间指出来。该论文有资格成为计算机科学史上著名的反例。因为他,后人会觉得不可思议并嘲笑,那个时代的程序员(不仅仅说他,包括我们)怎么这样愚蠢

关键是,还有太多人抱DIP大腿,本来我可以只宣讲针对接口编程/抽象依赖原则,对DIP不加理会。但是它到处出现,像苍蝇一样时不时烦人,而且有人这样介绍DiP、IoC和DI,让我们怎么学习IoC和DI?

为什么我要使用单词"倒置"inversion。坦白地说,这是因为比较传统的软件开发方法——例如结构化分析和设计,倾向于创建这样的软件结构:高层模块依赖于低层模块,并且抽象依赖细节。的确,这些方法的一个目标在于定义一个子程序层次以描述高层模块如何调用低层模块,....。因此,一个设计良好的面向对象程序的依赖结构,对应于传统的过程式方法通常会形成的依赖结构,是"倒置"

这就是胡扯了。首先,对工具箱的调用与框架设计,适用于各自的场合,没有高低贵贱之分。传统的过程式方法和良好的面向对象程序,都有对工具箱的调用,如Demo类调用java.lang .Math,而且JavaI/O工具箱有许多抽象类型(谁规定低层模块一定是具体类?)。传统的过程式方法和良好的面向对象程序,都可以设计框架。在他眼里,似乎过程式编程只会调用低层模块,面向对象程序才会依赖抽象类型、才会设计框架。很多人比较过程式编程范式与面向对象编程范式时,也常常犯这种错误。把女人中的好人和男人中的坏人加以比较,有意义吗?

从这段话可以推测,Robert C. Martin使用反转这个词,也是想区别函数库和框架。换言之,他心中希望将他的inversion和控制反转/IoC中的反转互通,但是他却只字不提IoC。

3)之所以将DIP拎出来批评,因为它对学习者带来了困扰【DIP对软件设计的学习造成的障碍越来越突出(随便举一个例子,《设计模式之禅》依赖倒置原则之问)[2];特别是依赖倒置、控制反转和依赖注入搅合在一起,成为“那些年搞不懂的高深术语”[3]】,因此,yqj2065需要明确地告诉大家:Robert C. Martin的那篇论文是垃圾

4)我的学生,我希望你们能够从该论文中列举5个错误。(不要文字/语文上的错误)。

在阅读本文之前,你需要做下面的实验,要求走如下5步。

实验1.框架设计者 

  1. SortorTest->BubbleSort。客户依赖具体的服务BubbleSort,地球人都知道这不好。
  2. SortorTest->IntSort<=BubbleSort。应用程序SortorTest遵循针对接口编程/抽象依赖原则,依赖抽象类型IntSort,而BubbleSort自然地依赖父类型。
  3. 【SortorTest->IntSort】。这一步是关键。如果需要,将控制模块SortorTest设计成框架,可以将控制模块SortorTest和它必须依赖的抽象类型IntSort打包。控制模块SortorTest从应用程序(上层模块)变为框架(下层模块),为控制反转(Inversion of Control)。最好能够提供JavaDoc,并且将方法名sort 改成soooooort。
  4. Main->【SortorTest->IntSort】<=BubbleSort。(其他程序员)使用框架。
  5. 带main的BubbleSort,BubbleSort->【SortorTest ->IntSort】<=BubbleSort。与第一步“依赖倒置”。

3.DIP的错误

(1)起点就错了。

一个设计良好的面向对象程序的依赖结构,对应于传统的过程式方法通常会形成的依赖结构,是"倒置"的』。这暴露了该论文的立论基础已经错了。

框架=控制反转(Inversion of Control),框架设计,或者说Inversion,与系统是不是采用“传统的软件开发方法”、“过程式方法”、“面向对象”,一点关系都没有。其论文后面将错就错,再怎样解释,只会使人不解。正因为DIP所言的倒置,本身就是胡扯,因此其鼓吹者不得不随意发挥,如《HeadFirst 设计模式》对依赖倒置的解说甚至是“倒置你的思考方式”。

(2) 含混的高层-低层模块(High-low level modules)

在介绍针对接口编程/抽象依赖原则时,不管采用什么术语,即不管是高层模块、控制模块或客户/Client,依赖者都不应该依赖“具体的”低层模块、步骤模块或Server,而应该依赖抽象类型IServer。使用控制模块通常意味着在框架场合讨论问题;而不论在分层场合,同层中或应用程序都使用C-S来进行一般性的讨论。

Robert C. Martin使用高层-低层模块,为什么他既不愿意将高层-低层模块等价于C-S,也不愿意使用分层架构中含义清晰的上层-下层?这种含义不清晰的术语最适合浑水摸鱼。

(3)

划线下面的文字,你凑合着看。

------------------------------------------------------------------------

 3.对DIP原文的解读,仅作为[2.1.5 抛弃依赖倒置原则]的素材资料。

很多编程的初学者困惑于“Java的接口有什么用”? 因此本章介绍软件设计的最基础的原则,依赖于抽象类型原则简称为抽象依赖原则[1]。

抽象依赖原则是开放封闭原则的主要内容,是“针对接口编程”的一种直观的说法。yqj2065将它作为软件设计的一个最为基础的原则,并将它写入给大学一年级学生使用的教材中。

一个最基本的原则,必须能够被读者简单明了地理解和接受。很多人和书籍讨论了Robert C. Martin于1996在一个专栏上发表的依赖倒置原则(Dependency Inversion Principle、DIP) ,该原则被收入到Robert C. Martin的著作《敏捷软件开发》中。一直以来,笔者虽然对该原则的表述存在疑虑,但简单地将DIP和针对接口编程等同看待。

本节明确提出抛弃依赖倒置原则,基于两点考虑。①如果DIP等于针对接口编程,则它是多余的、对软件设计的学习造成障碍的原则;②实际上,DIP是不知所云的、漏洞百出的原则。


2.DIP = = program to an interface ?

Robert C. Martin提出的DIP,包含3点内容:

1.        依赖倒置原则、Dependency Inversion Principle。原则的名字

2.        A:高层模块不应该依赖低层模块,两个都应该依赖抽象。High level modules should not depend upon low level modules, both shoulddepend upon abstractions.

3.        B:抽象不应该依赖细节,细节应该依赖抽象。Abstractions should not depend upon details, details should depend uponabstractions.
大量的书籍、文章将DIP作为针对接口编程/抽象依赖原则的花里胡哨又令人费解的同义词。此时,对于DIP的3点内容最好的做法是拒绝介绍或解释,然而这可能吗?于是出现了各种各样的DIP的解释。

“依赖倒置”?很多设计的初学者会问:“哪里体现依赖的倒置”?而《Head First 设计模式》的解答甚至是“倒置你的思考方式”,能不能再奇葩一点,倒置人的世界观啊。

依赖倒置就是从A依赖B,变成B依赖A。是的,就应该这么简单。但是,Robert C. Martin不会认为他的原则如此脑残。【“打包并使用SortorTest和接口IntSort ”,做过这个实验题的同学可以给出演示代码。但是当演示代码摆在面前,即使是既无胸又无脑的白痴也不会为它提出一个依赖倒置原则,不就是从SortorTest->BubbleSort变成BubbleSort->SortorTest。Java程序员习惯在具体类中都带一个main,你为带main的BubbleSort提出一个原则?哪有这样脑残的原则。所以,能够简单地加以说明的真正的依赖倒置,就死活不敢出来见人。】

以其昏昏使人昭昭。扯淡的要诀就是使用一大堆含义不明确、或者其他使用环境中需要的术语。或者往哲学上扯

DIP文章解读

抽象依赖原则中,

★客户Client不应该依赖具体类Server,应该依赖抽象类型IServer。
★(显然地,)子类型依赖父类型。父类型应该是抽象类型。

Robert C. Martin使用High -low level modules,我们将DIP解读成针对接口编程/抽象依赖原则,或者说将高层模块-低层模块换算成Client-Server真的是对DIP的误解。这一点暂时按下不表。

结论:

原则的名字:依赖倒置原则、Dependency Inversion Principle。得多么脑残才会为第一步与第五步的不同,得出一个依赖倒置原则。不知道有没有人发现,BubbleSort依赖SortorTest本身是违反依赖倒置原则的,BubbleSort应该依赖ITest(能不能再脑残一些呢?)。

规则A:高层模块/控制模块,不应该依赖具体的底层模块。如果底层模块能够设计成工具箱,高层模块依赖工具箱中的抽象类型;如果高层模块需要设计成框架,高层模块和抽象类型构成框架。

规则B:假话需要更多的假话来圆场。


  • 如果你将DIP作为面向接口编程的花里胡哨、不好理解的版本,不需要浪费时间继续阅读本文
  • 如果你想知道DIP到底想说什么,为什么DIP是Robert C. Martin 自我陶醉的胡扯,你必须真正懂得什么是IoC。

【下面将和大家一起解读DIP,yqj2065在本博客中就发布过The Dependency Inversion Principle(翻译)(2005-01-28 00:22)。为了正本清源,yqj2065打起精神重新读一下,并将明确提出抛弃依赖倒置原则。

很多人将这样的东西作为金科玉律,yqj2065必须告诉这些人:该论文真的很垃圾。】


DIP = program to an interface ?

【示例: Copy程序】中,Copy模块调用"Read Keyboard"等两个模块,由于"Read Keyboard"等是具体模块,因此Copy模块复用性差。在我上课的例子中,就是SortorTest-具体类BubbleSort的关系。这时,你应该明白,下面我们将应用抽象依赖原则、"program to an interface, not an implementation".

【依赖倒置】中,的确也是这样做的。SortorTest依赖接口IntSort,而BubbleSort依赖接口IntSort。SortorTest->IntSort<=BubbleSort.

但是,Robert C. Martin使用High -low level modules,"Copy()模块,它包含高层策略,依赖于它控制的底层细节性模块"........."然而Copy类根本不依赖"Keyboard Reader"和"Printer Writer",因此依赖性被反转了,Copy类和细节的readers、writers都依赖于相同的抽象"。这里,不管你懂不懂哪里体现依赖性被反转了,反正他就这样说了。

【依赖倒置原则】通过上面的叙述,“”现在可以陈述依赖倒置的一般形式了"

依赖倒置原理、DIP:
1. High level modules should not depend upon low level modules, both should depend upon abstractions.
2. Abstractions should not depend upon details, details should depend upon abstractions.

直到这里,不看后面(即使看了后面,很多人都会忽略后面的东西),除了我们不太满意没有采用我们容易理解的Client-Server,而是使用High -low level modules、不太理解"倒置"外,我们将DIP和面向接口编程等同看待。或者用yqj2065的话:

★抽象依赖原则:为了应对需求变化,代码中要尽可能地依赖抽象类型,而非具体类。

一直以来,yqj2065就是这样对待DIP的,而且你上网搜索"依赖倒置原则",99%的网站、文章里面也是这样介绍DIP的。

让我们捋一捋思路:既然我们有“针对接口编程”,为什么需要这个花里胡哨、不好理解的DIP呢?这个DIP应该有它独特的东西才合理,否则没有存在的必要。这些疑虑我们先埋在心里。

按照读到这里yqj2065的肤浅理解(或者说,DIP=面向接口编程这种包括我在内的大众的肤浅理解),依赖倒置原则除了花里胡哨的装逼外,也存在一些问题:

  1. 高层模块和底层模块,既然是程序员形容模块价值或某种地位的说法,你凭什么将底层模块默认为具体类型?JDK中工具库(非框架)中同样有大量的抽象类型。你的A原则,是不是要改成“”高层模块不应当依赖具体的底层模块“”,当然,你越改,越显得=面向接口编程,越没有存在的必要;
  2. 抽象属于高层模块还是底层模块?这是要害!依赖抽象类型原则不讨论这一点,而DIP没有说明它,而在这篇文章之外,DIP就凭这一点继续蒙人
  3. “抽象不应该依赖细节。细节应该依赖抽象”,你到底想说什么?如果抽象-细节是IServer和Server,IServer不可能依赖Server,如同"水不应该向上流",水从来就没有向上流过!

不管怎样,大众非常善良、或者说宽容地把DIP=面向接口编程,接受这个花里胡哨的装逼的原则。后面yqj2065继续和你一起阅读这篇文章,来证明这篇文章为什么垃圾。


依赖倒置是东施效颦的倒置/反转

“为什么我要使用单词"倒置"(“inversion”.)。坦白地说,这是因为比较传统的软件开发方法——例如结构化分析和设计,倾向于创建这样的软件结构:高层模块依赖于低层模块,并且抽象依赖细节。的确,这些方法的一个目标在于定义一个子程序层次以描述高层模块如何调用低层模块,图1是一个这样层次的好例子。因此,一个设计良好的面向对象程序的依赖结构,对应于传统的过程式方法通常会形成的依赖结构,是"倒置"的。”

DIP文章最垃圾之处,在于东施效颦地借用了控制反转(Inversion of Control)中的Inversion

控制反转指控制模块由上层搬到了下层,上课的例子中SortorTest有两种用法。

(1)在底层包util中或本包有BubbleSort等,而学生编写SortorTest(包含main)属于上层或同层模块。 上层模块必然依赖下层模块,如同你的app依赖JDK一样。
(2)如果SortorTest是老师编写的下层模块,要求你编写属于上层模块等BubbleSort,并且要求你使用SortorTest进行测试,这个SortorTest和接口IntSort 构成框架。

为了体会控制反转或者说设计框架,实验题就有打包并使用SortorTest和接口IntSort 。

Robert C. Martin 泛泛而谈地把“过程式方法”与“面向对象”依赖结构称为"倒置",给人一种你想驳斥又抓不到其痛点的困境。我们不谈“过程式方法”与“面向对象”是不是"倒置",就算你错了我们也不管。

但是我们强调一点,你能够用Java、也能够用C设计框架,Java通过多态,C通过函数指针。框架=控制反转(Inversion of Control)。框架设计中,Inversion 与“传统的软件开发方法”、“过程式方法”、“面向对象”一点关系都没有。是的,我们先堵住这篇文章狡辩的退路。

"想想高层模块依赖于低层模块的寓意。...是高层模块应该优于低层模块。...当高层模块不依赖低层模块时,高层模块可以被十分简单复用。.
正是这个原则,它是框架设计(framework design.)的核心。"

看见没有,

框架=控制反转(Inversion of Control),DIP是框架设计(framework design.)的核心。
1996的Robert C. Martin ,把含义清晰的控制模块的Inversion ,变成他的Dependency Inversion。当你考虑分层结构的依赖关系时,因为上层模块必然依赖下层模块,你又糊涂了,依赖怎样Inversion。

【分层(Layering)】既然该文偷用了控制反转(Inversion of Control)中的Inversion ,他为什么敢讨论分层?他为什么要讨论分层?

因为按照针对抽象(类型)编程,Client->Server变成Client->IServer<=Server,在一个包或层中,怎样都不好说明倒置。如果A依赖于B,那么改成B依赖于A,这可以叫倒置,这里Client->Server变成Client->IServer,怎样倒?

于是,他讨论分层。分层中倒置没有( 不问 什么东西倒置没有)倒置/反转了!!!因为 框架=控制反转(Inversion of Control)。

高层类 "Policy" 使用了低层类 "Mechanism","Mechanism" 使用了更细粒度的 "Utility" 类。

按照框架来设计,就是

这就是框架设计中基本的控制反转(Inversion of Control),上机实验打包的SortorTest和接口IntSort ,分别对应Policy Layer和Mechanism interface,使用框架时,上层程序员需要按照IntSort 编写各种排序代码。

这就再次证明,依赖倒置是东施效颦的倒置/反转,其实他的意思就是控制反转。然而,他技巧性的使用了高层、低层,没有使用分层架构中含义清晰的上层和下层,于是他的解读就成为了高层(下层)Policy Layer和Mechanism interface不再依赖低层(上层)的Mechanism Layer,而是低层依赖高层!(注意,虽然Mechanism 不依赖Policy,但是按照分层的依赖原则,分层的依赖是以整个层为依赖单元的,因此,他这样解读不算错误。)

通过各种技巧,搞出来一个“依赖倒置”。如果你将“依赖倒置”的Inversion ,按照控制反转(Inversion of Control)的Inversion 理解,一切ok。否则各种不适应。

抽象-细节

【C++中的接口与实现相分离】他意识到“不管怎么说,Policy层仅仅依赖于Mechanism层的接口。Mechanism层的实现(implementation)的变化怎么会向上最终影响到Policy层呢?在一些面向对象语言中,这可能是真的。...”

于是,开始借语言的不同来把水搅浑。前面我们不是困惑 “抽象不应该依赖细节。细节应该依赖抽象”到底什么意思吗。看他如何把水搅浑。如果抽象-细节是IServer和Server的关系,水怎样都搅不浑。但是,

1.-细抽象节就是抽象-细节,

2.“C++中,实现并不是自动与接口相分离的”。

3.再次给一个Client-Server级别的例子,具体Server(他的Lamp)"无论Lamp类何时改变,Button类就必须改变,或者至少要重新编译。"

[附:关于接口与实现相分离,这篇文章是混乱的.我不在这里讨论。简单地说,即使Client依赖具体的Server,Server代码的实现有变化只要方法的接口不变,也不影响Client。]

。“应用的高层(policy)没有与低层模块相分离;抽象没有与细节相分离。没有这样的分离,高层自动的依赖于低层模块,并且抽象也自动的依赖于细节”。

好了,你看到这里,你会感到“抽象也自动的依赖于细节”好像有道理,你忘记了,其实是Server和自己没有分离,Server自动的依赖于Server。

4.“为了与依赖倒置原则相符,我们必须把这种抽象与问题的细节相隔离。因而我们必须指导依赖性的设计,让细节依赖于抽象”。

到这里,一个基本的“子类型依赖父类型”,被他东搅西搅,成为了一个原则B。

那么,他到底到底想说什么?

仔细看“抽象不应该依赖细节。细节应该依赖抽象”。令抽象=A,细节=B,这个话就是“A不应该依赖B,B应该依赖A”,看见没有,多么完美的依赖倒置

如果你直接说子类型依赖父类型,那他还玩个屁啊。这就是原则B的真正含义,一个毫无价值的废话,都能够包装成令很多人脑洞大开的原则


我前面说了,这篇论文可以看成他学习《设计模式》的探索式笔记,有再多的问题都是正常的。问题是,在《敏捷软件开发》又玩新花样。

some important discussion on the concept of a package and interface ownership which are key to distinguishing this principle from the more general advise to "program to an interface, not an implementation" found within the book Design Patterns (Gamma, et. al).

这些东西我懒得穷追猛打了,前面的“这是要害”点明了。

同学们只要知道,在你做过的框架实验中 ,其包的关系是【SortorTest->IntSort】<=BubbleSort,你显然不会设计出SortorTest->【IntSort<=BubbleSort】这种不可能的东西。我们讨论不可能的东西,还不如看几部鬼片。

返回

[1]本节改编于《编程导论(Java)·4.4依赖于抽象类型》】

[2]该文写到:"依赖倒置原则可以说是六大设计原则中比较难理解的”、"对于“倒置”秦小波老师是从人的思维层面解读的"。我没有看秦小波的书,对于依赖倒置中“倒置”的任何解读,都是徒劳的。太多人在DIP上自由发挥。

[3]《道法自然——面向对象实践指南》一书的作者写的一篇文章《向依赖关系宣战——依赖倒置、控制反转和依赖注入辨析》【知网上有,csdn上找到一篇转载的】,完全与我的说法不在一个宇宙空间中,这种将3个自己没有搞清楚的概念混为一谈的文章,我连喷它的兴趣都没有。

[4]Robert C. Martin (Uncle Bob).Agile Software Development Principles,Patterns,and Practices(敏捷软件开发、3P) .Pearson Education.在《编程导论(Java)》中我将它列为推荐读物

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值