策略模式
提示
本文主要介绍Spring源码中涉及的策略模式应用
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。
# 1. 多重if-else
public int count(int num1, int num2, String operation) {
if (operation.equals("+")) {
return num1 + num2;
} else if (operation.equals("-")) {
return num1 - num2;
} else if (operation.equals("*")) {
return num1 * num2;
} else if (operation.equals("/")) {
return num1 / num2;
}
}
2
3
4
5
6
7
8
9
10
11
有没有觉得这些算法都耦合在一起了,如果需要改某个算法,而要改动整个count()方法,如果其中某个算法出错了,会影响整个count()方法。
# 2. 策略模式实现
# 2.1 定义一个策略接口: Strategy.class
public interface Strategy {
public int doOperation(int num1, int num2);
}
2
3
# 2.2 创建接口的实现类
public class Add implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 + num2;
}
}
public class Substract implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 - num2;
}
}
public class Multiply implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 * num2;
}
}
public class Divide implements Strategy{
@Override
public int doOperation(int num1, int num2) {
return num1 / num2;
}
}
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
# 2.3 创建Context类
public class Context {
private Strategy strategy;
public SetStrategy(Strategy strategy){
this.strategy = strategy;
}
public int executeStrategy(int num1, int num2){
return strategy.doOperation(num1, num2);
}
}
2
3
4
5
6
7
8
9
10
11
# 2.4 创建实现类
public class StrategyPatternDemo {
public static void main(String[] args) {
Context context = new Context();
context.SetStrategy(new Add());
System.out.println("10 + 5 = " + context.executeStrategy(10, 5));
context.SetStrategy(new Substract());
System.out.println("10 - 5 = " + context.executeStrategy(10, 5));
context.SetStrategy(new Multiply());
System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
context.SetStrategy(new Divide());
System.out.println("10 / 5 = " + context.executeStrategy(10, 5));
}
}
2
3
4
5
6
7
8
9
10
11
12
13
# 3. 策略模式的优缺点
# 3.1 优点
- 策略模式实现类的算法或行为可以自由切换
- 避免使用多重条件判断
- 扩展性能好
# 3.2 缺点
- 策略类增多
- 所有的策略类需要向外暴露
# 3.3 策略模式中的三个角色
- Context封装角色:也叫上下文角色,起到封装作用,屏蔽了高层模块对策略的直接访问
- 策略抽象角色:定义策略实现的接口
- 策略实现类:实现策略接口,实现具体的策略算法或行为内容并向外界暴露
# 4. 在Spring源码中应用的策略模式
在上图中,BeanDefinitionReader
、ResourceLoader
、BeanNameGenerator
三个接口作为策略接口,而其他的实现类都分别实现了各自的行为用于针对不同的业务场景,那么还有一个Context封装对象,在这里就是ApplicationContext——AbstractXmlApplicationContext里。
# 4.1 Spring源码中的BeanDefinitionReader
在学习BeanDefinitionReader之前,要先了解一下什么是BeanDefinition
public interface BeanDefinition extends AttributeAccessor, BeanMetadataElement {
String SCOPE_SINGLETON = ConfigurableBeanFactory.SCOPE_SINGLETON;
String SCOPE_PROTOTYPE = ConfigurableBeanFactory.SCOPE_PROTOTYPE;
...
boolean isSingleton();
boolean isPrototype();
boolean isAbstract();
...
}
2
3
4
5
6
7
8
9
可以看到BeanDefinition作为一个接口,主要是用于存储从XML配置文件读取Bean信息到JVM内存的一个载体,具体是存储在了BeanDefinition的实现类——RootBeanDefinition中,下面来看看RootBeanDefinition。
public class RootBeanDefinition extends AbstractBeanDefinition {
@Nullable
private BeanDefinitionHolder decoratedDefinition;
@Nullable
volatile ResolvableType targetType;
// 用于缓存给定bean定义的解析类型确定类
@Nullable
volatile Class<?> resolvedTargetType;
...
}
2
3
4
5
6
7
8
9
10
11
12
可以看到RootBeanDefinition不是真正存储Bean信息的载体,继续查看BeanDefinitionHolder
public class BeanDefinitionHolder implements BeanMetadataElement {
private final BeanDefinition beanDefinition;
private final String beanName;
@Nullable
private final String[] aliases;
}
2
3
4
5
6
7
8
9
看到了存储bean用的beanName和别名aliases数组了
回到正题,我们已经知道了BeanDefinition就是一个存储XML配置文件中bean信息的一个载体,那么这个过程是如何实现的呢?答案就在BeanDefinitionReader的实现类————XmlBeanDefinitionReader里面。
XmlBeanDefinitionReader就是一个策略的具体实现,表示的是一种可以从Xml中获取Bean配置信息的策略,除了这种策略外,还有PropertiesBeanDefinitionReader,这种从Properties配置文件获取Bean配置信息的策略。
第三节中总结提到在策略模式中有三种角色,1)Context封装角色;2)策略抽象角色;3)策略实现角色。在这里我们已经找到了策略抽象角色——BeanDefinitionReader和策略实现角色——XmlBeanDefinitionReader和PropertiesBeanDefinitionReader,就差Context封装角色了,那么Spring中哪个类充当了这个角色呢?
答案就是——AbstractXmlApplicationContext类
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// 为给定的BeanFactory创建一个新的XmlBeanDefinitionReader
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// 允许子类提供reader的自定义初始化
initBeanDefinitionReader(beanDefinitionReader);
// 真正加载bean定义信息
loadBeanDefinitions(beanDefinitionReader);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
可以看到,策略实现类XmlBeanDefinitionReader在AbstractXmlApplicationContext中执行了具体的策略执行,也就是后面复杂的从Xml配置文件读取bean配置信息的操作。
# 4.2 Spring源码中ResourceLoader
下面先看看ResourceLoader的源码,然后再来简单介绍下其作用
public interface ResourceLoader {
String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;
Resource getResource(String location);
ClassLoader getClassLoader();
}
2
3
4
5
6
7
8
这里的ResourceLoader就是一个Resource加载器,而Resource是将URL、URI等资源抽象为一个Resource资源对象,方便Spring进一步操作。
下面先来分析下三种角色:
AbstractBeanDefinitionReader
作为Context封装角色ResourceLoader
作为策略的抽象DefaultResourceLoader
和ResourcePatternResolver
就是具体的执行策略
由于resourceLoader instanceof ResourcePatternResolver为true
,所以走如下逻辑:
- AbstractBeanDefinitionReader.java
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
int count = loadBeanDefinitions(resources);
2
- AbstractApplicationContext.java
@Override
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
2
3
4
- PathMatchingResourcePatternResolver.java
@Override
public Resource[] getResources(String locationPattern) throws IOException {
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
return findPathMatchingResources(locationPattern);
}
else {
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
int prefixEnd = (locationPattern.startsWith("war:") ? locationPattern.indexOf("*/") + 1 :
locationPattern.indexOf(':') + 1);
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
return findPathMatchingResources(locationPattern);
}
else {
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
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
# 5. 总结与分析
- 策略模式是一个比较容易理解和使用的设计模式, 策略模式是对算法的封装 , 它把算法的责任和算法本身分割开 , 委派给不同的对象管理 。策略模式通常 把一个系列的算法封装到一系列的策略类里面 ,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
- 在策略模式中,应当由客户端自己决定 在什么情况下使用什么具体策略角色。
- 策略模式仅仅封装算法,提供新算法插入到已有系统中 ,以及老算法从系统中“退休”的方便 ,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。