行为型模式
目录
2.2.1 Spring MVC中 DispatcherServlet 使用策略模式
1、策略模式
在策略模式(Strategy Pattern)中,一个类的行为或其算法可以在运行时更改。
策略这个词应该怎么理解,打个比方说,我们出门的时候会选择不同的出行方式,比如骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。
再比如我们去逛商场,商场现在正在搞活动,有打折的、有满减的、有返利的等等,其实不管商场如何进行促销,说到底都是一些算法,这些算法本身只是一种策略,并且这些算法是随时都可能互相替换的,比如针对同一件商品,今天打八折、明天满100减30,这些策略间是可以互换的。
策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。
- 意图:定义一系列的算法,把它们一个个封装起来, 并且使它们可相互替换。
- 主要解决:在有多种算法相似的情况下,使用 if...else 所带来的复杂和难以维护。
- 何时使用:一个系统有许多许多类,而区分它们的只是他们直接的行为。
- 如何解决:将这些算法封装成一个一个的类,任意地替换。
- 关键代码:实现同一个接口。
1.1 策略模式UML图
1.2 日常生活中看策略模式与应用实例
- 诸葛亮的锦囊妙计,每一个锦囊就是一个策略。
- 旅行的出游方式,选择骑自行车、坐汽车,每一种旅行方式都是一个策略。
- JAVA AWT 中的 LayoutManager。
1.3 Java代码实现
需求:对交通工具计算车费,一般轿车,中等轿车,豪华轿车。
/*
* 计算费用接口
*
*/
public interface ICalculateStrategy {
/**
* 按距离计算车费
* @param km 公里数
* @return
*/
int calculatePrice(int km);
}
各种车的计算方式
//普通车
public class GeneralCar implements ICalculateStrategy {
@Override
public int calculatePrice(int km) {
if (km > 0 && km <= 5)
return 5;
if (km > 5 && km <= 7)
return 7;
if (km > 7 && km <= 10)
return 10;
return 10;
}
}
//中级车
public class MediumCar implements ICalculateStrategy {
@Override
public int calculatePrice(int km) {
if (km > 0 && km <= 5)
return 6;
if (km > 5 && km <= 7)
return 9;
if (km > 7 && km <= 10)
return 12;
return 12;
}
}
//高级车
public class LuxuryCar implements ICalculateStrategy {
@Override
public int calculatePrice(int km) {
if (km > 0 && km <= 5)
return 8;
if (km > 5 && km <= 7)
return 13;
if (km > 7 && km <= 10)
return 15;
return 13;
}
}
我们在创建一个操作 Context 策略的类
public class TransportationCalculator {
/**
* 交通工具计算费用策略类
*/
private ICalculateStrategy iCalculateStrategy = new GeneralCar();
/**
* 设置策略
* @param calculateStrategy
*/
public void setStrategy(ICalculateStrategy calculateStrategy) {
this. iCalculateStrategy = calculateStrategy;
}
public int calcu(int km){
return iCalculateStrategy.calculatePrice(km);
}
}
测试:
@Test
public void test8(){
TransportationCalculator transportationCalculator = new
TransportationCalculator();
System.out.println("普通车 1 km RMB:" + transportationCalculator.calcu(5));
transportationCalculator.setStrategy(new MediumCar());
System.out.println("中级车 1 km RMB:" + transportationCalculator.calcu(5));
transportationCalculator.setStrategy(new LuxuryCar());
System.out.println("豪华车 1 km RMB:" + transportationCalculator.calcu(5));
}
Output:
普通车 1 km RMB:5
中级车 1 km RMB:6
豪华车 1 km RMB:8
普通车 1 km RMB:5 中级车 1 km RMB:6 豪华车 1 km RMB:8
有没有发现这种写法不仅结构变得清晰,而且还易维护,扩展性也很强。
2、策略模式在源码中的应用
2.1 JDK源码中策略模式体现
在JDK中,我们调用数组工具类Arrays的一个排序方法sort时,可以使用默认的排序规则(升序),也可以自定义指定排序的规则,也就是可以自定义实现升序排序还是降序排序,方法的源码如下:
public static <T> void sort(T[] a, Comparator<? super T> c) {
if (c == null) {
sort(a);//若没有传入Comparator接口的实现类对象,也就是调用默认的排序方法
} else {
if (LegacyMergeSort.userRequested)
legacyMergeSort(a, c);
else
TimSort.sort(a, 0, a.length, c, null, 0, 0);
}
}
也就是说我们传递两个参数,一个是待排序的数组,另一个是Comparator接口的实现类对象,其中Comparator接口是一个函数式接口,接口里面定义了一个用于定义排序规则的抽象方法
int compare(T o1, T o2);
,由此可见,Comparator接口就是策略模式中的策略接口,它定义了一个排序算法,而具体的策略或者说具体的排序算法实现将由用户自定义实现。
1.不指定排序规则的情况:
//Arrays的sort方法默认是进行升序排序
public static void main(String[] args) {
int[] arr={10,11,9,-7,6,18,2};
Arrays.sort(arr);
System.out.println(Arrays.toString(arr));//[-7, 2, 6, 9, 10, 11, 18]
}
2.自定义降序策略(匿名类的写法)
public static void main(String[] args) {
//使用匿名类的写法
Comparator<Integer> c=new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
if(o1>o2){
return -1;
}else if(o1<o2){
return 1;
}else{
return 0;
}
}
};
Integer[] arr={10,11,9,-7,6,18,2};
Arrays.sort(arr, c);
System.out.println(Arrays.toString(arr));//[18, 11, 10, 9, 6, 2, -7]
}
3.自定义升序策略(Lambda表达式的写法)
public static void main(String[] args) {
//由于Comparator接口是函数式接口,可以使用Lambda表达式进行简化
Integer[] arr={10,11,9,-7,6,18,2};
Arrays.sort(arr, (o1,o2)->{
if(o1>o2){
return 1;
}else if(o1<o2){
return -1;
}else{
return 0;
}
});
System.out.println(Arrays.toString(arr));//[-7, 2, 6, 9, 10, 11, 18]
}
2.2 Spring源码中策略模式体现
2.2.1 Spring MVC中 DispatcherServlet 使用策略模式
DispatcherServlet在进行转发前需要进行传说中的九大件的初始化,其中去初始化时除了 initMultipartResolver(上传文件)没有获取 Properties defaultStrategies;默认策略,其他的八大件都会使用到策略模式。先看一下 defaultStrategies为 java.util.Properties类型,定义如下:
public class Properties extends Hashtable<Object, Object> {
// ***
}
流程梳理:
1、当Web容器启动时,ServletWebServerApplicationContext 初始化会调用其refresh()方法,则会调用 DispatcherServlet的onRefresh方法(Spring Ioc的流程可以参见:Spring Ioc和Mvc时序图)
2、onRefresh方法 - > initStrategies方法 -> 初始化九大件
3、初始化时则会调用getDefaultStrategy方法。如下:
4、getDefaultStrategy实现,就是去调用了Properties这个Hashtable的key对应的value值,如下:
protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
List<T> strategies = this.getDefaultStrategies(context, strategyInterface);
if (strategies.size() != 1) {
throw new BeanInitializationException("DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
} else {
return strategies.get(0);
}
}
protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
String key = strategyInterface.getName();
String value = defaultStrategies.getProperty(key);
if (value == null) {
return new LinkedList();
} else {
String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
List<T> strategies = new ArrayList(classNames.length);
String[] var7 = classNames;
int var8 = classNames.length;
for(int var9 = 0; var9 < var8; ++var9) {
String className = var7[var9];
try {
Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
Object strategy = this.createDefaultStrategy(context, clazz);
strategies.add(strategy);
} catch (ClassNotFoundException var13) {
throw new BeanInitializationException("Could not find DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", var13);
} catch (LinkageError var14) {
throw new BeanInitializationException("Unresolvable class definition for DispatcherServlet's default strategy class [" + className + "] for interface [" + key + "]", var14);
}
}
return strategies;
}
}
5、那么Properties的值在哪里添加进去的呢,DispatcherServlet 的static静态代码块中会看见,是用Spring的Resource将配置文件中的配置加载,设置到这个Map容器中的,如下:
static {
try {
ClassPathResource resource = new ClassPathResource("DispatcherServlet.properties", DispatcherServlet.class);
defaultStrategies = PropertiesLoaderUtils.loadProperties(resource);
} catch (IOException var1) {
throw new IllegalStateException("Could not load 'DispatcherServlet.properties': " + var1.getMessage());
}
}
6、在看看 DispatcherServlet.properties 配置就明白了,就是九大件没传时的默认值,其实也可以考虑用SPI机制实现(记得之前好像是不是有版本就是用SPI实现的)。
# Default implementation classes for DispatcherServlet's strategy interfaces.
# Used as fallback when no matching beans are found in the DispatcherServlet context.
# Not meant to be customized by application developers.
org.springframework.web.servlet.LocaleResolver=org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver
org.springframework.web.servlet.ThemeResolver=org.springframework.web.servlet.theme.FixedThemeResolver
org.springframework.web.servlet.HandlerMapping=org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter
org.springframework.web.servlet.HandlerExceptionResolver=org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver,\
org.springframework.web.servlet.mvc.annotation.ResponseStatusExceptionResolver,\
org.springframework.web.servlet.mvc.support.DefaultHandlerExceptionResolver
org.springframework.web.servlet.RequestToViewNameTranslator=org.springframework.web.servlet.view.DefaultRequestToViewNameTranslator
org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver
org.springframework.web.servlet.FlashMapManager=org.springframework.web.servlet.support.SessionFlashMapManager
小结: web容器启动,ServletWebServerApplicationContext 的refresh方法 间接调用到 DispatcherServlet的初始九大件方法, 其中八大件在没有自定义实现的情况下,调用默认的 配置。 而默认配置则是在 DispatcherServlet的静态代码块中,由Spring的ClassPathResource将配置文件DispatcherServlet.properties中的配置加载进一个 Map容器中。只待初始化九大件时,根据不同的九大件类型作为key,调用相应的实现。
2.2.2 实例化对象的时候用到了Strategy模式
在Spring中,实例化对象的时候用到了Strategy模式,图示如下所示:
在上图中:InstantiationStrategy为抽象接口,SimpleInstantiationStrategy实现接口,但是在方法instantiate中进行判断,针对bd中没有MethodOverrides的,直接通过jdk反射进行构造函数调用,而针对有需要针对方法做MethodOverrides的,则可以通过另一种方式处理,在SimpleInstantiationStrategy中是通过:instantiateWithMethodInjection()方法处理的,在CglibSubclassingInstantiationStrategy中对该方法做了override实现。CglibSubclassingInstantiationStrategy继承自SimpleInstantiationStrategy,对MethodInjection方法的实现如下:
@Override
protected Object instantiateWithMethodInjection(RootBeanDefinition bd, @Nullable String beanName, BeanFactory owner,
@Nullable Constructor<?> ctor, Object... args) {
// Must generate CGLIB subclass...
return new CglibSubclassCreator(bd, owner).instantiate(ctor, args);
}
通过static的class类CglibSubclassCreator进行instantiate操作,剩下的就是cglib内中的细节了,此处不分析。
策略模式中包含的部分是:
(1)抽象策略InstantiationStrategy接口
(2)具体策略SimpleInstantiationStrategy,被继承后子类CglibSubclassingInstantiationStrategy,实现自抽象策略接口(3)上下文对策略的引用,在AbstractAutowireCapableBeanFactory中是通过new CglibSubclassingInstantiationStrategy赋值给InstantiationStrateg的引用,进而进行具体的方法调用,具体见下方代码:
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory
implements AutowireCapableBeanFactory {
/** Strategy for creating bean instances. */
private InstantiationStrategy instantiationStrategy = new CglibSubclassingInstantiationStrategy();}
(注意:这里一般是通过对抽象策略接口的引用,之后通过多实现的根据当时具体选择的策略内容进行调用实现)
3、策略模式的优缺点
3.1 优点
- 结构清晰明了、使用简单直观;
- 耦合度相对而言较低,扩展方便;
- 操作封装也更为彻底,数据更为安全;
3.2 缺点
- 策略类数量会增多,每个策略都是一个类,复用的可能性很小
- 所有的策略类都需要对外暴露
3.3 应用场景
- 如果在一个系统里面有许多类,它们之间的区别仅在于它们的行为,那么使用策略模式可以动态地让一个对象在许多行为中选择一种行为。多个类只有算法或行为上稍有不同的场景
- 一个系统需要动态地在几种算法中选择一种。算法需要自由切换的场景
- 如果一个对象有很多的行为,如果不用恰当的模式,这些行为就只好使用多重的条件选择语句来实现。
- 需要屏蔽算法规则的场景
3.4 注意事项
如果一个系统的策略多于四个,就需要考虑使用混合模式,解决策略类膨胀的问题。
4、总结与分析
- 1)策略模式是一个比较容易理解和使用的设计模式, 策略模式是对算法的封装 , 它把算法的责任和算法本身分割开 , 委派给不同的对象管理 。策略模式通常 把一个系列的算法封装到一系列的策略类里面 ,作为一个抽象策略类的子类。用一句话来说,就是“准备一组算法,并将每一个算法封装起来,使得它们可以互换”。
- 2)在策略模式中,应当由客户端自己决定 在什么情况下使用什么具体策略角色。
- 3) 策略模式仅仅封装算法,提供新算法插入到已有系统中 ,以及老算法从系统中“退休”的方便 ,策略模式并不决定在何时使用何种算法,算法的选择由客户端来决定。这在一定程度上提高了系统的灵活性,但是客户端需要理解所有具体策略类之间的区别,以便选择合适的算法,这也是策略模式的缺点之一,在一定程度上增加了客户端的使用难度。
参考文章:
https://www.cnblogs.com/keke-xiaoxiami/p/11139563.html
https://zhuanlan.zhihu.com/p/139671042
https://blog.youkuaiyun.com/hguisu/article/details/7558249