1.2.5 迪米特法则

迪米特法则(Law of Demeter,LOD),又叫作最少知识原则(The Least Knowledge Principle),一个类对于其他类知道得越少越好。简单来说就是只暴露方法入口,而实现细节不需要暴露给调用者。

干涩的文字描述总是不能给读者带来清晰的认知,在本书后续项目落地设计模式实战的“多种类第三方支付”章节,会为大家带来“策略模式+门面模式+工厂模式+亨元模式”的落地实战。调用者只需要关心支付类型(如支付宝支付、微信支付等),无须关心具体的支付交互细节,“门面模式”就是一种遵循迪米特法则的、极具代表性的实现。

关于项目实战落地,我们后续章节细细道来。在这里,我更想和大家聊聊Spring源码中哪部分的设计是最具代表性的、最能体现遵循迪米特法则的设计。我们先来回忆一下Spring容器使用的简单示例,代码如下:

以上代码是Spring容器使用最为基础的示例,相信接触过Spring框架的读者对此都了然于心。从代码的书写过程中可以看到,想要使用Spring框架,我们仅仅关心以下三点即可。

①XML配置文件的创建或者标有@ComponentScan配置类的创建及注解使用。

②ApplicationContext的构造函数及入参。

③getBean方法的使用。

作为Spring框架的使用者,我们仅仅需要知道以上三点就可以了,可是Spring源码为我们做了很多。以注解容器AnnotationConfigApplicationContext的创建方式为例(注解方式是趋势,简单方便,避免对复杂XML配置文件的维护管理),我们来看一下Spring源码为我们做了什么,先来看容器创建的入口——AnnotationConfigApplicationContext的构造函数,代码如下:

构造函数很清晰,包含三行代码,接下来我们分别对这三行函数所包含的内容进行说明。

(1)this()方法。

this()方法调用了无参构造函数,我们来看看无参构造函数做了些什么,代码如下:

依然是很清晰的逻辑,表面看无非是创建了两个对象,但是这两行代码实际上为我们做了很多重要的底层逻辑,很抱歉不能够更深层次地进行源码的展示,但笔者会尽最大可能为大家解释这两行代码所完成的功能,如果你阅读过Spring源码必然会产生共鸣,如果你即将要阅读Spring源码必然会有所帮助,当然,你最终也会深刻地体会到Spring的这部分源码设计是如何遵循迪米特法则的。接下来我们来介绍以下这两行代码的功能。

①创建读取Annotation注解下的BeanDefinition阅读器,为Bean工厂添加比较器依赖,如@Order注解、@Priority注解,为Bean工厂添加延时加载依赖,如@Lazy注解。

②注册Spring依赖的BeanDefinition,如内部注解配置处理器internal-ConfigurationAnnotationProcessor、内部依赖注入处理器internalAutowiredAnnotatio nProcessor等。

③初始化Spring运行的Environment,如SystemProperties、systemEnvironment。

④基于“初始化子类之前需提前初始化父类”的规则,隐性地在父类GenericApplicationContext的无参构造中初始化Bean工厂核心类——DefaultListableBeanFactory。

⑤创建classPath的包扫描器,为后续自定义的Bean提供扫描功能。

这些都是我们无须关心的,各种Bean对象的读取、注册、扫描,甚至运行环境的初始化我们都无须关心。最小知识原则就体现在这里。

(2)register(componentClasses)方法。

我们先来看看该方法的源码,代码如下:

我们可以看到,该方法依靠我们在this()方法中创建的reader进行配置类的注册,最终会通过我们在“依赖倒置原则”所介绍的registerBeanDefinition方法进行BeanDefinition的注册,此处就不再过多地展开描述。

(3)refresh()方法。

这个方法是Spring容器初始化的核心,这个方法涉及的内容十分丰富,我们来看看它都做了些什么,代码如下(方法功能介绍请参见注释):

试想一下,如果Spring没有为我们做以上的功能,那么监听器要自己实现,消息处理器要自己实现,事件分发要自己考虑,这将是非常可怕的事情。Spring仅为使用者暴露了有限的、简单的方法调用入口,而将其他所有的功能隐藏起来,做到了最少知识原则,符合迪米特法则。