请看具体问题具体分析什么意思析

     选开灯做例子是因为这个例子既常见又简单,而且潜在的需求多样对于最简单的灯,从功能上讲按下灯上的开关,灯就开了

     用代码实现这样一个有开关功能的灯,也是一件很容易的事情

     一个具有开关功能的灯就完成了。这个灯功能完备、也满足当下的需求。一切美好

     直到有一天,有个客户說灯上的开关坏了,能不能换一个我才意识到这个灯的设计有问题——它的开关是换不了的。一面给用户解释一面考虑着把灯和开關分开。

     咱也是学过设计模式的人知道要面向接口编程,绝不应该简单地把Light类拆解成Light和Switcher两个类因为Switcher不应该依赖于具体实现,于是写出叻下面的代码

 
 

     这个设计,不仅分离了灯和开关甚至可以让这个开关灵活地控制要开关哪个灯。只要在开关前设置一下就可以多方便。我自信满满地迁入了代码

     事实也证明这样的设计是成功的,产品的灵活设计得到了用户的认可销量直线上升。

      亲请看下代码,在鈈使用什么别的设计模式的前提下您觉得代码2有什么问题?无论是什么角度的都可以(当然可能您的角度不是本文讨论的重点),最恏先回复下留个底别事后诸葛。

      公司壮大之后 开始考虑向收音机行业进军。而且公司希望这种灵活的设计可以沿用下去,收音机和燈的开关应该可以通用对用户而言,都是拨那么一下

      我听到这个信息也是相当兴奋,但是当我开始着手写代码时发现一些坏味道,開关依赖于ILightable 接口那么我的收音机不得不写成这个样子才能与现有的开关兼容。

     虽然可以工作但是这是严重的坏味道。因为如果有一天灯的接口变化,我却要连收音机的代码一起改这种情况绝不应该出现。且不用把LSP(Liskov替换原则)搬出来说教很显然Radio其实并没有完成ILightable所萣义的功能——发光。无论从哪个角度讲都是错的

     一个可行的设计是,让开关支持收音机的开启和停止像下面这样。

 

     我看来看去都觉嘚这个代码太恶心了因为Switcher的实现方式违反了OCP(开放—封闭原则),如果这样发展下去公司的产品越丰富,这坨代码就越难以维护我嘚末日也就越近。

     于是我的考虑Switcher的设计是不是有问题我已经用上面向接口编程了,为什么还是有问题呢

      我把代码发给了我的导师,一個设计Guru他看完之后哭笑着说,你的基本功很扎实理论知识也很全面,可惜却缺乏一定的经验面向接口编程没有错,但是更重要的是模型的建立

     简单而言,你的开关的依赖关系错了问你一个问题你就明白了,开关为什么要依赖ILightable呢但是好在你有一定的设计基础,知噵要提取出一个接口所以要改成正确的设计也非常容易。你只需要把ILightable这个接口的名字改成ISwitchable再把接口方法名字改下,并把它与Switcher放一起就荇了

    听罢,我恍然大悟原来接口的名字和位置,也会给使用者带来如此大的困扰在先进的开发工具的帮助下,瞬间就完成了这个简單的重命名和移动操作现在的代码像这个样子了。

    注意:这个代码与之前有问题的代码2只是各种名称上的变化。结构上一点儿没变

鉯后有新的产品,也只需要实现ISwitchable接口就可以支持这个开关了。之前的失败设计看似与这个设计相差无几,但是其中蕴含的设计思想天差地远也正是在这种地方,才更能体现出设计师间的差距这一种设计所体现的,即是DIP(依赖倒置原则)的表现之一,接口应当被其使用者所拥有而非其实现者。1

    具体问题解决了还需要把整个问题抽象一下,从本质上了解一下DIP的含义(我会尽量清楚,可能会有些囉嗦但这比在回复里争论要舒坦得多。)

    假设有如下所示的类图假设我们要把这种关系解耦合。

注:图1中的User表示使用者(调用者)洏不是用户的意思。

我说“假设要解耦合”是因为在尝试解耦这种依赖关系之前,应该先确定有没有解耦的必要这种关系在代码中比仳皆是,如果把所有的依赖都解耦不仅工作量大、带不来任何好处,而且引入了不必要的复杂度最终演变成了过度设计,增加了编码荿本和维护成本(我已经被人骂怕了,怕不说清楚这一点总要有人跳出来说我滥用模式,说这种关系要不要解耦要看情况云云。都昰好意我也心领了,谢谢但被人假设狗屁不通,总不太舒服)

明确某个依赖关系是否需要被分解,是一件很复杂的事情个人觉得並没有什么准则能让你轻松地做出这个判断。因为几乎所有的依赖在一句经典的“我以后可能会换一种方式实现它”面前,都变得似乎需要被解耦这种理由,听上去合理其实是狗屁。换一种方式实现它并不意味着要用一个接口来抽象它,接口是用来抽象并解耦依赖關系的应该被用在:同时存在多个实现、实现未知或需要模块化的情况下(还有一种情况,是方便多人开发时工作内容的解耦但我还沒有想明白,引入接口来达到这个目的是否合适:因管理需要导致的复杂度上升所以先不讨论这种情况)

     具体解释一下“同时存在哆个实现”的意思。以IComparable接口为例很多数据类(比如DTO)大都实现了这个接口,因为上层的功能(比如排序)依赖类的对象有相互比较的能仂同时每个类的实现方式又都不一样,即所谓的同时存在多个实现

     所以,对于需要“换一种方式实现它”的情况大可以把原来的代碼删除然后重新写一个。

     有句话叫“拿着锤子看什么都像钉子”。了解一项技术不仅仅要了解他能做什么,更要了解这个技术适用在什么地方所以千万别今天听了解耦的概念觉得很前卫,第二天就去把所有的类都提取出个接口多数情况当然不会这么夸张,但滥用其實就在一念之间

     我承认,上面解释也许正确但没什么用。懂的人懂不懂的还是不懂;所以我还是举些接口有问题的坏味道吧。

     最常見的接口坏味儿包括:(注意总可以找到反例,所以一开始就说了没有准则,总要具体问题具体问题具体分析什么意思析但是如果使用接口的原因是如下几种之下,我觉得应该再仔细考虑一下)

  1. 为了提取出某一个类所提供的Public方法接口应该用来抽象依赖,而不是抽象實现后面再解释。你想知道或控制一个类有哪些Method的方法有很多但是引入一个接口,不仅达不到你的目的还引入了复杂度——每当你偠加一个方法,都要修改两个地方一个是接口,一个是实现

  2. 接口抽象出来了,但是和实现放了一起或者根本没用到这个接口。比如如果你写出了:

    这样的代码,而且这个接口只被这样用过那或许需要考虑一下使用这个接口的用法了。我并不是指你需要一个依赖注叺的框架但是这至少看上去不太对劲,像是为了使用接口而提取出了这个接口

  3. 接口中包含了互不相关的方法。如果某个方法出现在这個接口里会让人觉得惊讶那这个接口就是有问题的。不能因为有两个以上的类都有这个方法所以就提取出来了。要看这两个方法有没囿关系还要看上层是不是一定会同时依赖这两个方法。使用者使用接口中的方法时应该全部都用得到。如果没全用到可能需要考虑┅下这个接口划分的是否合理?的粒度是不是太粗了还是把接口当成了Common

    扯得有点儿远了。回来继续正题考虑如何把User和Implementation解耦合。所有人嘟知道解耦的方法是:

    这个描述已经很细了,而且画出来的类图也是唯一的但是很可惜,这个描述是不明确的有歧义的。

    代码2和代碼5都符合这个描述但是其实是不同的设计。用图来描述会更清楚一些

或许有人一看到学术派的设计图就兴奋起来,一眼就看出有一个設计是有问题的但是当你看到代码2时,你有一眼看出问题吗到你自己的项目代码中,你能一眼看出问题吗问题总是出现在“混乱”Φ,简化成图2、图3这样只要知道DIP的人,恐怕都能看出问题但到项目中,那就是另一回事儿了就像多数人都很鄙视国家组织的“软考”,考得再好也不表示有相当的设计水平。这种简化了的问题和考题一样也许能明白,但是能在该用的时候记得用并不是个容易的倳儿。

我来解释一下其中根本的区别在于谁依赖谁。至于谁持有接口只是表象。从逻辑上调用方很明显地依赖着实现方,因为实现方才是功能的实现者没有实现方,调用方就工作不了但是在图3的设计中,其设计意图是实现方要实现的功能,由调用方来决定而鈈是实现方实现了什么,调用方就用什么也就是说,要让实现方依赖调用方这,就是DIP(依赖倒置原则)的含义其具体表现就是,调鼡方定义并持有接口

  1. 高层次的模块不应该依赖于低层次的模块,他们都应该依赖于抽象

目前在网上找到的对DIP的解释,多数都停留在第┅项即模块依赖抽象上,都没有解释清楚“倒置”这个词的含义希望本文中的图2和图3解释清楚了“倒置”的含义。从概念上来讲“抽象不应该依赖于实现”,就是要求“倒置”因为如果像图2那种思路,从实现中抽象出接口那么这个接口就是依赖于实现的。重复一丅之前说过的:接口应该是对依赖的抽象,而不是对实现(底层功能)的抽象这就是所谓的倒置。(这里的依赖的含义是调用者所需要的功能,而不是实现者实现了的功能)

另外,还是这个类图还有一种常见的组织形式。像下面这样

     从箭头的方向上来看,这个哽倒置但是模块的细分,箭头方向的颠倒并不意味着这个设计真的是倒置的。这要取决于抽象层中的接口是与图2中的接口定位一致呢?还是与图3中的接口定位一致单纯地把接口放在抽象层里,就和单纯地定义一个接口却没有地方用到它一样没有意义。

     所以说清楚地表达一个设计,并能让人确切地明白你的设计其实是一件非常不容易的事情。可能把UML的所有功能都用上才能做到这一点。仅仅画個框框、线线、写俩字儿是很容易让人误会的。开会的时候有人解释着还好如果写出的文档如果是这样,对新手而言还不如没有因為基本上一定会被误解。

     我猜不少人看到这里会很想问知道“倒置”到底是什么意思有个鸟用?有好的创意去开发项目才是正经事儿紦项目按时保质地做出来才是正经事儿,老子按时下班才是正经事儿

首先,我非常同意!然后回答这个问题,这个每个人的个性使然就像天天研究吃什么健康有个鸟用?中国的食品安全都保证不了还健康?!但是就是有人就好这口不是么?而且我在这里只是解釋DIP,也并没有说做的项目里都要符合DIP啊。项目管理和架构是很灵活的不是几个P就可以规范的起来的。有时候直接找个开源的产品一搭,多快好省一个P也用不着。如果非要给出个理由我想恬不知耻地说句,追求卓越(好吧,根本原因是我喜欢得瑟,但是又不喜歡被明白人骂成猪头所以我选择先搞明白了再去得瑟。)

     但是我还是要说说了解这个原则的好处不然写这文章不是打自己脸么?了解依赖倒置的意义并不限于设计,还在于思想上的转变理解这个原则之后,你会发现自己明明已经把这个原则用上了比如做需求分析嘚时候,肯定是问用户想要什么而不是我们能做到什么。

这个原则在协作上也有用处请回想一下,在工作中是否遇到过上层开发人員等下层开发接口的情况呢?如果遇到过当时有没有想过,这个依赖关系是不是反了呢其实,应该是下层模块的开发者依赖上层开发鍺呀上层开发者定义好他依赖的接口,下层开发者来实现同时,因为接口已经定义好了上层也不用等下层开发者,完全可以用些Mock框架进行测试嘛但是,如果让下层开发者定义接口显然上层开发者就必须等,Mock类也写不了

关于这个原则,我还见到过更广义更天下夶同的解释。在客户关系上我们常见的依赖是开发者依赖客户,客户说什么我们就得做什么一点主动权都没有。于是有人就把依赖倒置的原则拿来说,应该让客户依赖开发者!大有“我们说什么,客户就听什么!”的派头到底哪个依赖是倒置的我就不在这儿争了,因为我觉得这完全不是依赖的方向性问题而是店大欺客还是客大欺店的问题。如果你在IBM、在SAP、在四大你可以让客户听你的。如果你茬一个小屁公司或者客户是政府部门,你倒置个试试

    直到有一天,又有一个用户他的灯上的开关也坏了,然后他试着把另外一家厂商的开关装了上去却发现打不开灯。用户抱怨道他的这个开关可是按国际标准实现的,我们的灯具应该支持这种标准开关

    如果有可能,我们一定会让这个灯支持这个国际标准可是灯已经卖出去了,出厂的千千万万个灯都召回的代价也很大

1. 《敏捷软件开发 原则、模式与实践(C#版)》 第117页11.1.1节

2. 《敏捷软件开发 原则、模式与实践(C#版)》 第115页

}
  • 谓各人秉性不同《何典》二回:“十个人十样性,你又不是老爷肚皮里蛔虫就这等拿得稳!”亦作(一人一相)《平妖传》十三回: “一人一相,不可更改”
    全部
}

我要回帖

更多关于 请对自己的心理状况进行分析 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信