当前位置:   article > 正文

【源码分析设计模式 11】SpringMVC中的策略模式_spring 策略模式

spring 策略模式

一、基本概念

什么是策略模式?

策略这个词应该怎么理解呢,打个比方说,我们出门的时候选择不同的出行方式,比如步行、骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。

再比如我们去逛商场,商场现在正在搞活动,有打折的、满减的、返利的等等,其实不管商场如何进行促销,说到底都是一些算法,这些算法本身就是一种策略,并且这些算法是随时可能互相替换的,比如针对同一件商品,今天打八折、明天满100减30,这些策略间是可以互换的。

策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。

二、策略模式的优缺点

1、优点

(1)算法可以自由切换

(2)避免使用多重条件判断

(3)扩展性良好,增加一个策略只需实现接口即可

2、缺点

(1)策略类数量会增多,每个策略都是一个类,复用性很小

(2)所有的策略类都需要对外暴露

三、策略模式的应用场景

1、何时使用

对于业务开发来说,业务逻辑的复杂是必然的,随着业务发展,需求只会越来越复杂,为了考虑到各种各样的情况,代码中不可避免的会出现很多if-else。

一旦代码中if-else过多,就会大大的影响其可读性和可维护性,而且代码显得很low。

策略模式完美的解决了ifelse的烦恼!

2、方法

将这些算法封装成一个一个的类,任意的替换

3、应用实例

  • 出行方式,自行车、汽车等,每一种出行方式都是一个策略
  • 商场促销方式,打折、满减等
  • java AWT中 LayoutManager ,即布局管理器

4、注意事项

如果一个系统的策略多于四个,就需要考虑使用混合模式解决策略类膨胀的问题

640?wx_fmt=jpeg

四、代码实例

下面就以商场促销为例使用策略模式实现商场促销算法。UML图如下:

1、上下文类

首先声明一个 CashSuper 对象,通过构造方法,传入具体的收费策略, getResult() 方法的功能为根据收费策略的不同获取计算结果。

  1. package designMode.strategy;
  2. public class CashContext {
  3. private CashSuper cashSuper;
  4. public CashContext(CashSuper cashSuper) {
  5. this.cashSuper = cashSuper;
  6. }
  7. public double getResult(double money){
  8. return cashSuper.acceptCash(money);
  9. }
  10. }

2、现金收费抽象类

策略类,为抽象类,抽象出收费的方法供子类实现。

  1. package designMode.strategy;
  2. public abstract class CashSuper {
  3. public abstract double acceptCash(double money);
  4. }

3、正常收费子类

  1. package designMode.strategy;
  2. public class CashNormal extends CashSuper {
  3. @Override
  4. public double acceptCash(double money) {
  5. return money;
  6. }
  7. }

4、打折收费子类

  1. package designMode.strategy;
  2. public class CashRebate extends CashSuper {
  3. private double moneyRebate = 0.8;
  4. public CashRebate(double moneyRebate) {
  5. this.moneyRebate = moneyRebate;
  6. }
  7. @Override
  8. public double acceptCash(double money) {
  9. return money*moneyRebate;
  10. }
  11. }

5、返利收费子类

  1. package designMode.strategy;
  2. public class CashReturn extends CashSuper {
  3. private double moneyConditation = 0.0;
  4. private double moneyReturn = 0.0d;
  5. public CashReturn(double moneyConditation, double moneyReturn) {
  6. this.moneyConditation = moneyConditation;
  7. this.moneyReturn = moneyReturn;
  8. }
  9. @Override
  10. public double acceptCash(double money) {
  11. double result = money;
  12. if(money>moneyConditation){
  13. result = money-Math.floor(money/moneyConditation)*moneyReturn;
  14. }
  15. return result;
  16. }
  17. }

6、client客户端

  1. package designMode.strategy;
  2. import java.util.Scanner;
  3. public class Client {
  4. public static void main(String[] args) {
  5. CashContext cashContext = null;
  6. Scanner scanner = new Scanner(System.in);
  7. System.out.println("请输入打折方式(1/2/3):");
  8. int in = scanner.nextInt();
  9. String type = "";
  10. switch (in){
  11. case 1:
  12. cashContext = new CashContext(new CashNormal());
  13. type += "正常收费";
  14. break;
  15. case 2:
  16. cashContext = new CashContext(new CashReturn(300,100));
  17. type +="满300返100";
  18. break;
  19. case 3:
  20. cashContext = new CashContext(new CashRebate(0.8));
  21. type += "打八折";
  22. break;
  23. default:
  24. System.out.println("请输入1/2/3");
  25. break;
  26. }
  27. double totalPrices = 0;
  28. System.out.print("请输入单价:");
  29. double price = scanner.nextDouble();
  30. System.out.println("请输入数量:");
  31. double num = scanner.nextDouble();
  32. totalPrices = cashContext.getResult(price * num);
  33. System.out.println("单价:" + price + ",数量:" + num + ",类型:" + type + ",合计:" + totalPrices);
  34. scanner.close();
  35. }
  36. }

7、运行结果

五、策略模式与工程模式的区别

1、策略模式是行为性模式,适应行为的变化 ,强调父类的调用子类的特性 。

工厂模式是创建型模式,适应对象的变化,强调统一接口 。

2、策略模式封装行为,调用的时候必须先制定实例化具体的类,再调用抽象的方法; 策略模式的作用是让一个对象在许多行为中选择一种行为。

工厂模式封装对象,实例化对象后调用的时候要知道具体的方法。

3、策略模式是调用不同类方法, 工厂模式是对父类进行重写。

这俩个模式本来就是解决类似的问题,可以说是孪生兄弟,且内部实现都差不多,都是通过子类来覆盖父类而已,不过简单工厂是把父类直接摆在客户端,而策略模式是将父类隐藏在Context里面,这样封装更好。

4、举个例子
(1)产品之于加减乘除,水果之于苹果梨橘子香蕉,文具之于笔尺刀,这时产品比较具体、有限和没有多个算法重叠,这时实用简单工厂模式。
(2)产品之于商场促销中的返利(可为300返100、500返200、10000返500等等无数)、折扣(2折、2.5折、6折、9折、9.1折等等无数)、正常购买、消费积分(100元10积分、200元30积分等等无数),这时产品构造又多次重叠,且有在不同时刻应用不同的规则时使用策略模式。

5、总结

简单工厂模式只是解决了对象的创建问题,工厂需要包括所有的产品对象的创建,如果产品对象形式经常变化,就需要经常改动工厂,以致代码重新编译。所以策略模式就诞生了,策略模式---它定义了算法家族,分别封装起来,而不是像简单产品模式一样定义所有的产品类,让他们之间可以互相替换,此模式让算法的变化,不会影响到使用算法的客户,使客户拥有相同的访问过程。
简单工厂模式的核心是“简单工厂模式就是用来封装所有的产品对象的”。

策略模式理解核心是“策略模式就是用来封装算法的,但在实践中,我们发现可以用它来封装几乎任何类型的规则,只要在分析过程中遇到需要在不同时间应用不同的业务规则,就可以考虑使用策略模式处理这种变化的可能性”。

在基本的策略模式中,选择所用的具体实现的算法的职责由客户端对象承担,并转给策略模式的Context对象。这是策略模式本身纯粹的定义,所以,“选择所用最终怎样处理”还有很多文章可做。
看了课本之后对于这两个模式还是有很多不理解的地方,但是相信随着对设计模式进一步的学习,能够更好地体会到这其中的奥妙,学习是一个循序渐进的过程。

六、SpringMVC中的策略模式

1、getDefaultStrategies

在DispatchServlet中的初始化组件中,用到了getDefaultStrategies方法,来决定不同组件的默认类型以实现组件的初始化操作。

  1. // 传入ApplicationContext上下文和策略接口的Class类型
  2. protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
  3. // 相应组件的类名
  4. String key = strategyInterface.getName();
  5. // 从property中获取当前策略接口实现类的类名集合
  6. String value = defaultStrategies.getProperty(key);
  7. if (value != null) {
  8. // 获取策略接口所有实现类的类名
  9. String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
  10. List<T> strategies = new ArrayList<T>(classNames.length);
  11. for (String className : classNames) {
  12. try {
  13. // 创建相应实现类的bean,并放入集合中
  14. Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
  15. Object strategy = createDefaultStrategy(context, clazz);
  16. strategies.add((T) strategy);
  17. }
  18. catch (ClassNotFoundException ex) {
  19. throw new BeanInitializationException(
  20. "Could not find DispatcherServlet's default strategy class [" + className +
  21. "] for interface [" + key + "]", ex);
  22. }
  23. catch (LinkageError err) {
  24. throw new BeanInitializationException(
  25. "Error loading DispatcherServlet's default strategy class [" + className +
  26. "] for interface [" + key + "]: problem with class file or dependent class", err);
  27. }
  28. }
  29. // 返回策略接口实现类的集合
  30. return strategies;
  31. }
  32. else {
  33. return new LinkedList<T>();
  34. }
  35. }
  1. // 初始化真正调用的重载方法,默认返回策略实现类的第一个
  2. protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
  3. List<T> strategies = getDefaultStrategies(context, strategyInterface);
  4. if (strategies.size() != 1) {
  5. throw new BeanInitializationException(
  6. "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
  7. }
  8. return strategies.get(0);
  9. }

我们可以看到,DispatchServlet在初始化组件时,会传入相应组件的接口,获取到该组件的实现类集合,并将第一个实现类作为默认的组件使用,例如我们来看initLocaleResolver方法,它初始化了一个默认的本地化处理组件。

  1. private void initLocaleResolver(ApplicationContext context) {
  2. try {
  3. this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
  4. if (logger.isDebugEnabled()) {
  5. logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
  6. }
  7. }
  8. catch (NoSuchBeanDefinitionException ex) {
  9. // We need to use the default.
  10. this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
  11. if (logger.isDebugEnabled()) {
  12. logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
  13. "': using default [" + this.localeResolver + "]");
  14. }
  15. }
  16. }

它传入了LocaleResolver.class,这个类有多个实现类,包括AcceptHeaderLocaleResolver、CookieLocaleResolver、FixedLocaleResolver等,对应了多种不同的处理方式,你可以决定用哪一种处理方式(绑定对应的组件就好了)。但试想一下,如果用if-else来决定用那种处理方式,光一个LocaleResolver,代码就将变得又长又臭,更何况springMVC还要初始化这么多其他组件。策略模式就用了面向对象的思想,用接口、继承、多态来代替if-else,增加了代码的可读性和可维护性。

2、其他使用到策略模式的地方

springMVC在决定request的media types时也用到了策略模式。其中的ContentNegotiationManager是最核心的一个类,其中有一个方法我们可以重点来看一下:

  1. @Override
  2. // 处理请求的mediaTypes
  3. public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
  4. // 遍历绑定的strategy来处理请求,其中ContentNegotiationStrategy是一个接口,它的实现类包含了多种决定mediaTypes的策略
  5. for (ContentNegotiationStrategy strategy : this.strategies) {
  6. // 如果某一策略实现类识别的请求的mediaTypes,则返回,未识别则继续遍历
  7. List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
  8. if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
  9. continue;
  10. }
  11. return mediaTypes;
  12. }
  13. return Collections.emptyList();
  14. }

ContentNegotiationStrategy接口只有一个方法:

  1. public interface ContentNegotiationStrategy {
  2. // 返回请求的mediaTypes集合,如果无法识别则抛出异常
  3. List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
  4. throws HttpMediaTypeNotAcceptableException;
  5. }

跟上面的LocaleResolver接口一样,ContentNegotiationStrategy也有多种不同的实现类,这些实现类大都对应了不同的处理方式。策略模式在此情形下,使得在处理request的mediaTypes时,省去了大量if-else判断,实现了策略算法与调用逻辑上的解耦。

3、总结

从上面两个简单的例子中我们可以看到,使用策略模式的实现方式是:

(1)定义策略接口->实现不同的策略类->利用多态或其他方式调用策略;

(2)从springMVC处理request的media types中,我们又可以学到:当我们遇到的问题时,如果无法事先知道哪种处理方式合适,可以使用策略模式。当某一种策略模式匹配时,返回正确结果,以此解决问题;

此外,策略模式使用的场景,实现的方式还有很多,这里就不一一赘述了。

总之,使用策略模式对我们处理和解决问题、算法(解决方式)和使用解耦、代码的可读性和可维护性方面都有极大的好处。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/IT小白/article/detail/63437
推荐阅读
相关标签
  

闽ICP备14008679号