迪米特法则,结合其含义又称之为“最少知道原则”,即一个类作为一个调用方,应当对自己依赖的类(被调用的类)其中所处理的逻辑细节,知道的越少越好。对于被依赖的类(被调用的类)不管在使用上多么的复杂,它都应尽量将处理逻辑封装在它的内部,对调用方提供简洁明了的公共方法即可,以此减轻上层调用方过多承担复杂逻辑的压力和变化。
1.2.朋友和陌生人对于程序编码设计是否遵循了“迪米特法则”,我们通常可以使用一段经典的描述来判断,该描述是:“只和朋友通信,不和陌生人说话”。那么对于这段话中什么是朋友,什么是陌生人,下面对其进行一个介绍。
每个对象都会与其他对象之间都存在一定程度的耦合关系,其中主要耦合方式包含:依赖、关联、组合、聚合等等。如果某个类作为被调用者,在其调用方的类中的内部主要体现为:类中成员变量的类型、方法参数类型、方法返回值类型,那么该类就属于调用者的“朋友”。
对于某个类而言,当它作为“被调用者”在“调用者”当中没有作为:类中成员变量的类型、方法参数类型、方法返回值类型,这些形式出现在类中,而是仅作为局部变量出现在某个方法体中,那么对于这种情况,该类就属于“陌生人”。
1.3.高内聚低耦合
如果类在编写时产生了大量与“陌生人说话”,那么这样会导致依赖的类具有隐匿性,如果“陌生人”发生改动,那么其他使用“陌生人”的类(调用方)很难精确的做出协调,需要改动的地方也很难预估,这样就加剧了类与类之间的耦合程度,你很难断绝对某个类的依赖。
另外,本来处理逻辑是类自己内部可以处理的,如果非要将处理逻辑带到上层(调用方),这样就导致上层(调用方)违背了“最少知道原则”,并且不符合高内聚(自己的事情自己做)。
由此可见,迪米特法则的核心思想就是让编码设计实现:高内聚,低耦合。
2.举例
我们的程序最终要想实现迪米特法则”,其实就是遵循“最少知道原则”,具体实现这个原则就要程序做到“只和朋友通信,不和陌生人说话”。这些介绍在理解层面上都是相对抽象的,接下来我将通过生活中的例子和代码来更通俗的体会迪米特法则。
2.1.生活场景
在实际的生活当中,人们都可以通过点外卖的方式来解决自己用餐的需求,接下来我将以点外卖解决用餐需求作为背景,来类比“迪米特法则”中的概念。
张三心血来潮突然想吃“全聚德”的菜肴,但是他又不想出门走上几公里的路去门店吃。于是他在网上查找发现了“全聚德”刚刚开设的外送模式,他哽咽着口水赶忙的打电话预定了外送订单。
由于“全聚德”在外送模式下刚刚开设还在起步阶段,有些外送门路还不是很了解,全聚德一开始将外送员、厨师、食材一起带到张三家,在张三家现场制作菜肴。在家中现场制作的过程中经常发生一些问题:张三在看到制作流程后经常会指手画脚导致味道“四不像”、厨师由于身体情况做了几道菜后就经常去拉肚子导致糊锅。
这些问题导致了张三并没有享受到了外送模式的便捷,因为当前的外送模式,全聚德让张三知道了太多的菜肴制作细节并且于过多的人产生联系,对于这个场景参照到“迪米特法则”来说,就是没有让使用方(张三)遵循“最少知道原则”,让使用者过多知道一些细节,从而增加了复杂程度。
另外在外送模式下,全聚德让过多的人员参与到了外送当中,其中有外送员、厨师、食材。其实张三作为顾客并不用和厨师、食材打交道,这样只会增加用餐的依赖程度,张三其实只用和餐馆的外送员打交道即可。那么这里的场景参照到“迪米特法则”当中,对于外送员而言和张三就是“朋友”,厨师、食材是“陌生人”,上面的例子中,厨师在现场制作菜肴经常因为身体情况拉肚子,就是体现出依赖了“陌生关系”,从而增加了耦合度并因此带来不可预估的风险。
“全聚德”为了实现更好的外卖用餐体验,使用迪米特法则对外送模式进行改良,之后的张三在不用关心更多的菜肴制作细节和对厨师的依赖,他只用接到外送员外送的菜肴,就可以快捷的享受到美食。
2.2.代码案例
在通过生活场景和迪米特法则概念进行类比后,我们接着这个例子通过代码案例在加深对迪米特法则的理解,以及使用它的好处。代码案例有两种形式,一种是没有遵循迪米特法则的例子,另一个是遵循了迪米特法则的例子,后者是针对前者存在问题改进,通过对比的形式更能突出该原则的优势。
未使用迪米特法则
1 class Person 2 { 3 4 public void 点外卖(外送员 foodDelivery,string 菜名) 5 { 6 厨师 张大师= foodDelivery.通知餐馆安排人员制作菜肴(); 7 var 菜肴= 张大师.烹饪(菜名); 8 9 foodDelivery.送餐(菜肴); 10 11 Console.WriteLine($"送餐成功,开始吃{菜肴}"); 12 13 } 14 } 15 16 class 外送员 17 { 18 public 厨师 通知餐馆安排人员制作菜肴() 19 { 20 return new 厨师(); 21 } 22 public string 送餐(string 菜肴) 23 { 24 Console.WriteLine("根据路线进行送餐"); 25 return 菜肴; 26 } 27 } 28 29 class 厨师 30 { 31 public string 烹饪(string 菜名) 32 { 33 Console.WriteLine("烹饪制作"); 34 return 菜名; 35 } 36 }
我们主要看Person类中的代码,Person类在当前代码中作为一个调用者(使用方),在点外卖的方法中使用到了两个类,一个是外送员类和厨师类。那么基于本文中对“朋友与陌生人”的概念,此时的朋友其实就是外送员类,因为它作为一个方法的参数类型包含在类中,是符合朋友的标准。对于厨师类而言就是陌生人,因为它作为局部变量在类的方法中,那么对于以上代码很显然就违反了迪米特法则。
使用迪米特法则
1 class Person 2 { 3 4 public void 点外卖(外送员 foodDelivery,string 菜名) 5 { 6 var 菜肴= foodDelivery.送餐(菜名); 7 Console.WriteLine($"送餐成功,开始吃{菜肴}"); 8 } 9 } 10 11 class 外送员 12 { 13 public 厨师 通知餐馆安排人员制作菜肴() 14 { 15 return new 厨师(); 16 } 17 public string 送餐(string 菜名) 18 { 19 var 菜肴 = 通知餐馆安排人员制作菜肴().烹饪(菜名); 20 21 Console.WriteLine("根据路线进行送餐"); 22 return 菜肴; 23 } 24 } 25 26 class 厨师 27 { 28 public string 烹饪(string 菜名) 29 { 30 Console.WriteLine("烹饪制作"); 31 return 菜名; 32 } 33 }
通常作为顾客并不关心菜肴的制作过程或外送员送餐的路线,他只关心外送员能够在预期时间进行送餐,使他能吃上饭。所以该代码案例主要将菜肴的制作环节从Person类中取掉,将菜肴的制作推向外部,这样就做到了让Person类遵循“最少知道原则”,另外切断点外卖方法中与厨师类(陌生人)的联系,促使Person类只和“朋友通信”(外送员),这样就实现了迪米特法则。
3.补充
类与类之间的耦合是不可避免的,遵循迪米特法则并不是完全解除依赖,而是在一定程度上降低类与类之间不必要的依赖。例如其中的某个概念:“不和陌生人说话”,这就并不是一个能够完全杜绝的情况,我们只能尽量的不要让陌生的类作为局部变量出现在类的内部,并且保障类对自己依赖的类知道的越少越好。
这从侧面也反映了设计模式原则并不是一个教条,而必须遵守,作为编码者更重要的是提炼其中的思想,根据实际的情况尽量的让程序设计做到最大化的“高内聚,低耦合”。
知识改变命运 【文章原创作者:武汉网站优化公司 http://www.5h5q.com/wzyh/ 复制请保留原URL】