随笔 - 34  文章 - 2 评论 - 58 trackbacks - 0

写在文章前:

  或许你写过无数代码,参与过很多大型系统的设计,但,你是否曾经思考过,你的设计可扩展、易维护么,在高速变化的互联网世界里,它能经得起这种急速变化的考验么?如果你没想过这些问题,那请先放下你那些牛逼的梦想,放下你的高傲,好好去理解、回味设计六大原则和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

posted on 2016-06-30 19:12  转瞬之夏  阅读(...)  评论(... 编辑 收藏