报童、钱包和迪米特法则(设计模式迪米特原则经典论文翻译)
写在文章前:
或许你写过无数代码,参与过很多大型系统的设计,但,你是否曾经思考过,你的设计可扩展、易维护么,在高速变化的互联网世界里,它能经得起这种急速变化的考验么?如果你没想过这些问题,那请先放下你那些牛逼的梦想,放下你的高傲,好好去理解、回味设计六大原则和23种设计模式,因为它们是你腾飞的基石。今天,我勇敢的尝试翻译一篇有关设计原则的经典论文,希望对大家有帮助。(翻译是一项很费时、费精力的活,而且博主英语水平也不是特别好,翻译时多采用意译,见谅)
前言
在我读大学的时候,我的一个教授说每个程序员都有一个“装满各种小技巧的包”,这些小技巧,也就是解决问题的方法,在我们的个人经历中,会一遍又一遍的用到。他说,他的工作就是“把更多的小技巧装入我们的包里”。他所说的“小技巧”也就是如设计模式和设计风格(原文为idioms)。
这篇论文要讨论的是一个我特别喜欢的“小技巧”。相对于被划为设计模式(经管常被划分为设计模式),把它划为设计风格可能更合适。在理解了这个设计风格并知道怎么使用它,我相信你的代码会更优雅、更不容易出错,且更易维护。我马上要讲述一个如何减少你代码中对象耦合的方法。这个方法有一个很棒的名字:迪米特法则
现在我们假设有一个送报的小孩,他从他的顾客那里得到送报的钱。让我们为顾客定义一个类,几行代码表示送报小孩从顾客收钱,和一个送报小孩会执行的一个代码片段。
编写我们的代码
好,让我们开始写顾客这个类。顾客类可能有个姓和名,可能有一个银行账户,或者购物地址等。为了例子的简单考虑,我们定义了一个简单的顾客类。
考虑到倒霉的长度,我们省略了setters。从这个例子看,很显然我们还需要定义一个钱包类:
好了,这是一个简单的钱包。以后可能给它添加各种数据结构,但这个例子,这个钱包足够了。
现在利用这些,我们可以开始做一些有用的事了。让我们的送报小孩按响门铃,然后向顾客要求送报的报酬。如果我们把这写成代码,代码如下:
这段代码表示送报小孩从顾客那拿到钱包,检查钱包以确保钱够,然后把钱从顾客钱包取出,放到自己钱包。
为什么这样很糟糕
那么,这段代码糟糕在哪?好,让我们把这段代码用真实的生活场景还原。
很显然,当送报小孩索要报酬时,顾客直接让小孩从自己的口袋取出钱包,并从钱包里拿走报酬。
我不知道你会不会这样,反正我是很少让人拿走我的钱包。在现实中,上面的场景会遇到很多困难,更不用说相信那小孩很诚实,只拿走自己应得的报酬。如果以后钱包有信用卡啥的,他还能拿走信用卡。但是,这个问题的本质是,“小孩知道了更多他不需要知道的信息”
那是一个很重要的概念。“送报小孩”这个类现在知道顾客有个钱包,而且还能操作它。当我们编译小孩这个类,我们还需要顾客和钱包这两个类。这三个类紧紧的耦合在了一起。如果我们改变钱包类,我们可能还需要修改其他两个类。
还有一个经典的难题可能会遇到。如果顾客的钱包被偷了,那会发生什么?在我们这个例子中,或许昨晚有个贼就偷了这个钱包,然后有人把代码修改了,把钱包置为null,如下:
victim.setWallet(null);
这看起来像是一个很合理的假设。但是对于我们的送报小孩类呢?回头看看代码。我们假设钱包是存在的!我们的代码执行时将抛出空指针异常。
我们可以在任何方法调用钱包类之前,检查它是否为空,但这会使我们的代码更乱。而真实场景会更坏–“如果我的顾客有钱包,就看看钱包里有多少钱……如果他能付钱,拿走钱”。很显然,我们被弄晕了。
修改原始代码
解决这个问题的更合适的方式是采用符合现实场景的方法:“当小孩来到门前要求报酬时,顾客不会把钱包交给小孩,而小孩甚至都不需要知道顾客有个钱包”。
注意,顾客不再有”getWallet()”方法了,但是他有一个getPayMent方法。让我们看看小孩的:
为什么现在更好
首先,现在的模型更好的符合现实世界的情况。小孩现在是向顾客索要报酬,而没有机会拿到钱包。
其次,钱包现在可以改变了,但小孩和这种变化彻底隔离开来了。如果钱包的被偷了,顾客会换个钱包,但只要顾客提供的接口保持不变,顾客的客户端不会关心顾客是否换了一个新钱包。代码变得更易维护了。
第三,可能也是最面向对象的理由,我们可以随意改变\’getPayment()\’的实现了。在第一个例子,我们假设顾客有一个钱包,这导致了我们讨论的空指针异常。在现实世界里,当小孩来到门前,我们的顾客可能从抽屉里取出钱,或者从室友那借。但所有这些业务逻辑,小孩都不需要关心了。所有这些都在\’getPayment()\’中实现,并且在将来可以改变而不影响小孩的代码。
你也可以听起来更智慧
在开头我提到过这关概念有个名字,叫做“迪米特法则”
一个方法的对象应该只引用该方法中的四种对象(注:可以简单称为”只与直接朋友通信”):
1、它自己
2、它的方法参数
3、它创造或者实例化的任何对象
4、有直接关联的对象
什么时候、怎么使用迪米特法则
1、get的链
最常用到的地方就是,如下
value = object.getX().getY().getTheValue();
(注:通过不停的get获得不同的对象,这样就与很多非直接朋友通信了,所以可以简单记为“减少点”,不过并不是“点多就是违反法则”。不过有篇文章介绍了,请参考The Law of Demeter Is Not A Dot Counting Exercise
)
2、很多临时对象
结论
我希望这篇论文能带给你一些新的东西。或许这些几乎就像常识一样普通。如果真的是这样,那很好。但是,理解它,给它一个统一的命名,然后人们可以使用它相互交流也是很好的。
原文请参考
http://www.ccs.neu.edu/research/demeter/demeter-method/LawOfDemeter/paper-boy/demeter.pdf