目录
一、迪米特法则是什么?
迪米特法则(Law of Demeter,LOD),又称为最少知识原则(Least Knowledge Principle,LKP) ,诞生于 1987 年美国东北大学一个名为迪米特的研究项目。它的核心思想非常简单:一个对象应该对其他对象有最少的了解,只和 “朋友” 通信,不和 “陌生人” 说话。
怎么理解这句话呢?打个比方,你是一个 “社恐” 患者,在社交场合中,你只和自己熟悉的朋友交流,对于那些不认识的陌生人,你不会主动去攀谈。在软件设计里,每个类就像是一个独立的个体,也应该尽量减少与其他不必要类的交互,只和直接关联的类打交道。这样一来,当某个类发生变化时,对其他类的影响就能降到最低,从而提高整个系统的稳定性和可维护性。
迪米特法则中的 “朋友”,指的是与当前对象有紧密联系的对象,包括以下几种:
- 当前对象本身;
- 当前对象的成员对象;
- 当前对象所创建的对象;
- 当前方法的参数对象。
一个对象应该只与这些 “朋友” 对象进行交互,而避免与其他对象产生不必要的依赖。 比如说,小明(一个对象)有一部手机(成员对象),他用手机(与成员对象交互)打电话给朋友小红(方法参数对象)。在这个场景里,手机和小红就是小明的 “朋友”,小明和这些 “朋友” 通信,符合迪米特法则 。要是小明不通过手机,而是直接去和小红的手机通信,就违反了迪米特法则,因为这引入了不必要的复杂性和依赖关系。
二、深入剖析迪米特法则
(一)法则核心:最少知识原则
迪米特法则的核心,即最少知识原则,要求对象之间的交互尽可能简单直接。就像在一个公司里,每个员工只专注于自己的本职工作和与直接相关的同事协作,而不是对公司里所有部门和人员的工作都了如指掌。在软件系统中,一个类不应该依赖过多其他类的实现细节,只需要了解与自己直接交互的类的必要信息 。这样,当某个类的内部实现发生变化时,对其他类的影响就会最小化,从而降低了系统的维护成本和出错风险。
假设我们正在开发一个电商系统,其中有一个订单类 Order 和一个支付类 Payment 。订单类只需要知道支付类提供了支付的方法,而不需要了解支付类内部是如何进行支付验证、与第三方支付平台交互等细节。订单类只和支付类进行必要的通信,调用支付类的支付方法完成支付操作,这就符合最少知识原则。如果订单类深入了解支付类的内部实现,一旦支付类的实现方式发生改变,比如更换了第三方支付平台,订单类也需要进行大量修改,这显然不利于系统的维护和扩展。
(二)“朋友圈” 划定:谁是直接朋友?
在迪米特法则中,明确 “直接朋友” 的概念至关重要。直接朋友是指那些与当前对象有紧密联系,出现在成员变量、方法参数、方法返回值中的类 。这些类与当前对象的交互是直接且必要的,就像我们生活中的亲密朋友,经常有密切的往来。
还是以上述电商系统为例,在订单类 Order 中,如果有一个支付对象 Payment 作为成员变量,用于处理订单的支付操作,那么 Payment 类就是 Order 类的直接朋友 。又比如订单类的某个方法接收一个用户类 User 作为参数,用于验证订单所属用户的信息,此时 User 类也是 Order 类的直接朋友 。因为这些类与订单类的交互是直接且必要的,它们之间的耦合关系是合理的。
与之相对的是,局部变量中的类通常不是直接朋友。因为局部变量的作用范围仅限于方法内部,它与当前对象的联系相对松散,就像在社交场合中偶尔认识的人,只是短暂交流,并非真正的 “朋友”。如果在一个类的方法中频繁创建和使用局部变量来与其他类交互,就可能违反迪米特法则,增加类之间不必要的耦合。
(三)广义与狭义:法则的不同视角
迪米特法则有广义和狭义之分 。狭义的迪米特法则强调类之间通信的直接性,即如果两个类不必彼此直接通信,那么这两个类就不应当发生直接的相互作用 。如果其中一个类需要调用另一个类的某个方法,可以通过第三者转发这个调用 。这种方式可以降低类之间的直接耦合,但可能会引入一些中介类,增加系统的复杂性。
比如在一个社交网络系统中,用户 A 和用户 C 之间没有直接的联系,但用户 A 想要获取用户 C 的一些公开信息。按照狭义的迪米特法则,用户 A 不应该直接与用户 C 交互,而是通过他们共同的好友用户 B 来获取这些信息 。用户 B 在这里就充当了一个中介的角色,转发用户 A 的请求给用户 C,并将结果返回给用户 A。
广义的迪米特法则则更侧重于类的设计和信息隐藏,涵盖了类的结构设计、信息流量和流向以及信息的影响控制等方面 。它要求在类的划分上,创建有弱耦合的类,每个类都应当尽量降低成员的访问权限,将不必要的信息隐藏在类的内部 。同时,在对其他类的引用上,一个对象对其他对象的引用应当降到最低 。通过这种方式,实现系统的高内聚、低耦合,提高系统的可维护性和可扩展性。
在设计一个图形绘制系统时,不同类型的图形类(如圆形类 Circle、矩形类 Rectangle 等)应该将自己的绘制逻辑和内部状态封装起来,只对外提供必要的接口方法 。其他类在使用这些图形类时,只需要调用这些接口方法,而不需要了解图形类内部的实现细节 。这样,当需要修改某个图形类的绘制算法时,不会影响到其他使用该图形类的部分,体现了广义迪米特法则的思想。