写在开头

记得大学刚毕业那会儿,想学点东西,于是拿出了《Head First 设计模式》这本书,就开始了阅读,我曾对这些模式感到晦涩难懂。然而,随着工作岁月的增长,我逐渐领悟到设计模式的价值,尤其是在实践中,特别是在Spring这样的大型设计框架中。

刚开始接触设计模式时,我常常感到困惑。这些模式的概念和实现方式似乎遥不可及,就像是编程世界中的高塔一样,让人望而却步。然而,随着不断地学习和实践,我渐渐明白了设计模式的真正价值所在。

设计模式并不仅仅是一些理论概念,它们是一种解决常见问题的实用方法,是编写优雅、高效代码的利器。在工作中,我越来越多地意识到,设计模式不仅仅是理论上的东西,而是可以直接应用于实践的工具。

特别是在与Spring框架的设计与开发中,设计模式发挥了极其重要的作用。Spring框架本身就是一个设计模式的典范,它采用了诸如依赖注入、工厂模式、代理模式等多种设计模式,使得框架具有高度的灵活性和可扩展性。

因此,我想借此机会将我在设计模式与Spring框架结合实践中所获得的经验分享给大家。通过这个专栏,我希望能够帮助更多的人理解设计模式的精髓,以及它们在实际项目中的应用。让我们一起探索设计模式的奥秘,以及它们在大型框架设计中的实战价值!

该专栏按照如下大纲进行编写,首先会介绍设计原则,在理解完设计原则后,我们深入了解每一种设计模式及其在 Spring 框架中的应用。

image-20240303225038336

从设计原则出发

设计模式的核心是一系列设计原则,它们为软件设计提供了基本的指导方针。在阅读Spring框架的设计时,我们也将遵循这些设计原则,并结合Spring框架的实践,探讨如何将这些设计原则应用于框架的设计与实现。

1. 单一职责原则(Single Responsibility Principle - SRP)

Spring 框架中的各个组件(如控制器、服务、数据访问对象等)都遵循了单一职责原则,每个组件都专注于执行特定的任务,从而提高了代码的内聚性和可维护性。

这里给出 Spring 框架中几个类,大家去感受一下:

  • XmlBeanDefinitionReader : 负责加载 XML 类型资源的 BeanDefinition(Bean 的元信息)。
  • AutowiredAnnotationBeanPostProcessor : 负责 @Autowired 注解注入依赖的实现(方法注入、字段注入)。

2. 开放-封闭原则(Open-Closed Principle - OCP)

Spring框架通过面向接口编程和依赖注入等机制,实现了对扩展的开放和对修改的封闭。框架的核心功能可以在不修改原有代码的情况下进行扩展和定制,从而提高了系统的可扩展性和灵活性。

这一项,Spring 中更是到处可见,就拿 BeanFactory 体系来举例,我们先来看下 BeanFactory 及其部分派生接口:

image-20240303230000486

大家在 Spring 源码中可以自行搜索看下每个接口中的功能定义,每个接口各司其职(单一职责),新增功能不会堆在一个接口内,例如 ListableBeanFactory 继承 BeanFactory,在负责 BeanFactory 功能的同时,扩展了 Bean 集合查找的特性。

3. 里氏替换原则(Liskov Substitution Principle - LSP)

Spring 框架中的各个组件都遵循了里氏替换原则,子类对象可以替换父类对象并且不影响程序的正确性。这样保证了框架的稳定性和可扩展性。

这里给大家举个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Demo {
public static void main(String[] args) {
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 重点关注 XmlBeanDefinitionReader 构造器
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(beanFactory);
reader.loadBeanDefinitions("classpath:/META-INF/merged-bean-definition.xml");

SuperUser superUser = beanFactory.getBean("superUser", SuperUser.class);
System.out.println(superUser);
}
}
// 在构造器中,实际入参为 BeanDefinitionRegistry
public XmlBeanDefinitionReader(BeanDefinitionRegistry registry) {
super(registry);
}

如上述示例待所示,XmlBeanDefinitionReader 构造器的形参要求是 BeanDefinitionRegistry,实际上我们传入的是 DefaultListableBeanFactory,程序照常运行,这就体现里氏替换的设计原则,DefaultListableBeanFactory 是 BeanDefinitionRegistry 的子类。

4. 依赖倒置原则(Dependency Inversion Principle - DIP)

Spring框架通过控制反转(IoC)和依赖注入(DI)等机制,实现了依赖倒置原则。高层模块不依赖于低层模块的具体实现,而是依赖于抽象,从而降低了模块之间的耦合度,提高了系统的灵活性和可维护性。

这个大家相比更深有体会了,不依赖具体实现也就意味着我们可以任意变更其具体实现来控制程序的不同行为而应用不受影响,降低了模块之间的耦合度。下面给大家举个例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
package com.markus.desgin.mode;

/**
* @author: markus
* @date: 2024/3/3 11:15 PM
* @Description:
* @Blog: https://markuszhang.com
* It's my honor to share what I've learned with you!
*/
public class Demo {

Animal animal;

public Demo(Animal animal) {
this.animal = animal;
}

public void cry() {
this.animal.cry();
}

public static void main(String[] args) {
Demo demo = new Demo(new People());
demo.cry();

demo = new Demo(new Dog());
demo.cry();

demo = new Demo(new Cat());
demo.cry();
}

interface Animal {
void cry();
}

static class People implements Animal {

@Override
public void cry() {
System.out.println("呜呜呜~");
}
}

static class Dog implements Animal {

@Override
public void cry() {
System.out.println("汪汪汪~");
}
}

static class Cat implements Animal {

@Override
public void cry() {
System.out.println("喵喵喵~");
}
}
}

5. 接口隔离原则(Interface Segregation Principle - ISP)

Spring框架合理设计了各个组件之间的接口,遵循了接口隔离原则。每个接口只包含客户所需要的方法,避免了不必要的依赖关系,提高了系统的灵活性和可维护性。

还拿刚才的 BeanDefinitionReader 举例,它依赖 BeanDefinitionRegistry,而我们知道 BeanDefinitionRegistry 在 Spring 框架中的唯一实现就是 DefaultListableBeanFactory。而在设计的时候遵循了接口隔离原则,并没有将 DefaultListableBeanFactory 写在这里。BeanDefinitionReader 的作用就是读取指定的资源解析出 BeanDefinition 并将其注册到 IoC 容器中,而 BeanDefinitionRegistry 就是干这活的哈哈,所以这里就引入了 BeanDefinitionRegistry。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public interface BeanDefinitionRegistry extends AliasRegistry {

/**
* Register a new bean definition with this registry.
* Must support RootBeanDefinition and ChildBeanDefinition.
* @param beanName the name of the bean instance to register
* @param beanDefinition definition of the bean instance to register
* @throws BeanDefinitionStoreException if the BeanDefinition is invalid
* @throws BeanDefinitionOverrideException if there is already a BeanDefinition
* for the specified bean name and we are not allowed to override it
* @see GenericBeanDefinition
* @see RootBeanDefinition
* @see ChildBeanDefinition
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException;

/**
* Remove the BeanDefinition for the given name.
* @param beanName the name of the bean instance to register
* @throws NoSuchBeanDefinitionException if there is no such bean definition
*/
void removeBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

/**
* Return the BeanDefinition for the given bean name.
* @param beanName name of the bean to find a definition for
* @return the BeanDefinition for the given name (never {@code null})
* @throws NoSuchBeanDefinitionException if there is no such bean definition
*/
BeanDefinition getBeanDefinition(String beanName) throws NoSuchBeanDefinitionException;

/**
* Check if this registry contains a bean definition with the given name.
* @param beanName the name of the bean to look for
* @return if this registry contains a bean definition with the given name
*/
boolean containsBeanDefinition(String beanName);

/**
* Return the names of all beans defined in this registry.
* @return the names of all beans defined in this registry,
* or an empty array if none defined
*/
String[] getBeanDefinitionNames();

/**
* Return the number of beans defined in the registry.
* @return the number of beans defined in the registry
*/
int getBeanDefinitionCount();

/**
* Determine whether the given bean name is already in use within this registry,
* i.e. whether there is a local bean or alias registered under this name.
* @param beanName the name to check
* @return whether the given bean name is already in use
*/
boolean isBeanNameInUse(String beanName);

}

6. 最少知识原则(Least Knowledge Principle - LKP)

Spring框架通过合理设计组件之间的关系,遵循了最少知识原则。对象之间的依赖关系尽可能简化,每个对象对其他对象有尽可能少的了解,从而提高了系统的可维护性和可扩展性。

意思就是除了该类该暴露出去的方法,将其余方法的访问权限设置为私有,不让使用该类的对象感知。

image-20240303232951089

本文总结

总结一下,本文是《重读 Java 设计模式:探索经典之道与 Spring 框架的设计》的第一篇,算是开了个头,主要介绍了写这个专栏的初衷以及设计原则。

设计模式的演进是一个不断迭代、不断优化的过程。在接下来的系列文章中,我们将深入探讨经典的设计模式,并结合Spring框架的实践,重新阅读和重写它们,以期提供更加灵活、可维护和高效的解决方案。让我们一起踏上这段设计之旅,探索设计模式与Spring框架的结合之美!