赞
踩
什么是策略模式?
策略这个词应该怎么理解呢,打个比方说,我们出门的时候选择不同的出行方式,比如步行、骑自行车、坐公交、坐火车、坐飞机、坐火箭等等,这些出行方式,每一种都是一个策略。
再比如我们去逛商场,商场现在正在搞活动,有打折的、满减的、返利的等等,其实不管商场如何进行促销,说到底都是一些算法,这些算法本身就是一种策略,并且这些算法是随时可能互相替换的,比如针对同一件商品,今天打八折、明天满100减30,这些策略间是可以互换的。
策略模式(Strategy),定义了一组算法,将每个算法都封装起来,并且使它们之间可以互换。
(1)算法可以自由切换
(2)避免使用多重条件判断
(3)扩展性良好,增加一个策略只需实现接口即可
(1)策略类数量会增多,每个策略都是一个类,复用性很小
(2)所有的策略类都需要对外暴露
对于业务开发来说,业务逻辑的复杂是必然的,随着业务发展,需求只会越来越复杂,为了考虑到各种各样的情况,代码中不可避免的会出现很多if-else。
一旦代码中if-else过多,就会大大的影响其可读性和可维护性,而且代码显得很low。
策略模式完美的解决了ifelse的烦恼!
将这些算法封装成一个一个的类,任意的替换
如果一个系统的策略多于四个,就需要考虑使用混合模式解决策略类膨胀的问题
下面就以商场促销为例使用策略模式实现商场促销算法。UML图如下:
首先声明一个 CashSuper 对象,通过构造方法,传入具体的收费策略, getResult() 方法的功能为根据收费策略的不同获取计算结果。
- package designMode.strategy;
-
- public class CashContext {
- private CashSuper cashSuper;
-
- public CashContext(CashSuper cashSuper) {
- this.cashSuper = cashSuper;
- }
-
- public double getResult(double money){
- return cashSuper.acceptCash(money);
- }
- }
策略类,为抽象类,抽象出收费的方法供子类实现。
- package designMode.strategy;
-
- public abstract class CashSuper {
- public abstract double acceptCash(double money);
- }
- package designMode.strategy;
-
- public class CashNormal extends CashSuper {
- @Override
- public double acceptCash(double money) {
- return money;
- }
- }
- package designMode.strategy;
-
- public class CashRebate extends CashSuper {
- private double moneyRebate = 0.8;
-
- public CashRebate(double moneyRebate) {
- this.moneyRebate = moneyRebate;
- }
-
- @Override
- public double acceptCash(double money) {
- return money*moneyRebate;
- }
- }
- package designMode.strategy;
-
- public class CashReturn extends CashSuper {
- private double moneyConditation = 0.0;
- private double moneyReturn = 0.0d;
-
- public CashReturn(double moneyConditation, double moneyReturn) {
- this.moneyConditation = moneyConditation;
- this.moneyReturn = moneyReturn;
- }
-
- @Override
- public double acceptCash(double money) {
- double result = money;
- if(money>moneyConditation){
- result = money-Math.floor(money/moneyConditation)*moneyReturn;
- }
- return result;
- }
- }

- package designMode.strategy;
-
- import java.util.Scanner;
-
- public class Client {
- public static void main(String[] args) {
- CashContext cashContext = null;
- Scanner scanner = new Scanner(System.in);
- System.out.println("请输入打折方式(1/2/3):");
- int in = scanner.nextInt();
- String type = "";
- switch (in){
- case 1:
- cashContext = new CashContext(new CashNormal());
- type += "正常收费";
- break;
- case 2:
- cashContext = new CashContext(new CashReturn(300,100));
- type +="满300返100";
- break;
- case 3:
- cashContext = new CashContext(new CashRebate(0.8));
- type += "打八折";
- break;
- default:
- System.out.println("请输入1/2/3");
- break;
- }
- double totalPrices = 0;
- System.out.print("请输入单价:");
- double price = scanner.nextDouble();
- System.out.println("请输入数量:");
- double num = scanner.nextDouble();
- totalPrices = cashContext.getResult(price * num);
- System.out.println("单价:" + price + ",数量:" + num + ",类型:" + type + ",合计:" + totalPrices);
- scanner.close();
- }
- }

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对象。这是策略模式本身纯粹的定义,所以,“选择所用最终怎样处理”还有很多文章可做。
看了课本之后对于这两个模式还是有很多不理解的地方,但是相信随着对设计模式进一步的学习,能够更好地体会到这其中的奥妙,学习是一个循序渐进的过程。
在DispatchServlet中的初始化组件中,用到了getDefaultStrategies方法,来决定不同组件的默认类型以实现组件的初始化操作。
- // 传入ApplicationContext上下文和策略接口的Class类型
- protected <T> List<T> getDefaultStrategies(ApplicationContext context, Class<T> strategyInterface) {
- // 相应组件的类名
- String key = strategyInterface.getName();
- // 从property中获取当前策略接口实现类的类名集合
- String value = defaultStrategies.getProperty(key);
- if (value != null) {
- // 获取策略接口所有实现类的类名
- String[] classNames = StringUtils.commaDelimitedListToStringArray(value);
- List<T> strategies = new ArrayList<T>(classNames.length);
- for (String className : classNames) {
- try {
- // 创建相应实现类的bean,并放入集合中
- Class<?> clazz = ClassUtils.forName(className, DispatcherServlet.class.getClassLoader());
- Object strategy = createDefaultStrategy(context, clazz);
- strategies.add((T) strategy);
- }
- catch (ClassNotFoundException ex) {
- throw new BeanInitializationException(
- "Could not find DispatcherServlet's default strategy class [" + className +
- "] for interface [" + key + "]", ex);
- }
- catch (LinkageError err) {
- throw new BeanInitializationException(
- "Error loading DispatcherServlet's default strategy class [" + className +
- "] for interface [" + key + "]: problem with class file or dependent class", err);
- }
- }
- // 返回策略接口实现类的集合
- return strategies;
- }
- else {
- return new LinkedList<T>();
- }
- }

- // 初始化真正调用的重载方法,默认返回策略实现类的第一个
- protected <T> T getDefaultStrategy(ApplicationContext context, Class<T> strategyInterface) {
- List<T> strategies = getDefaultStrategies(context, strategyInterface);
- if (strategies.size() != 1) {
- throw new BeanInitializationException(
- "DispatcherServlet needs exactly 1 strategy for interface [" + strategyInterface.getName() + "]");
- }
- return strategies.get(0);
- }
我们可以看到,DispatchServlet在初始化组件时,会传入相应组件的接口,获取到该组件的实现类集合,并将第一个实现类作为默认的组件使用,例如我们来看initLocaleResolver方法,它初始化了一个默认的本地化处理组件。
- private void initLocaleResolver(ApplicationContext context) {
- try {
- this.localeResolver = context.getBean(LOCALE_RESOLVER_BEAN_NAME, LocaleResolver.class);
- if (logger.isDebugEnabled()) {
- logger.debug("Using LocaleResolver [" + this.localeResolver + "]");
- }
- }
- catch (NoSuchBeanDefinitionException ex) {
- // We need to use the default.
- this.localeResolver = getDefaultStrategy(context, LocaleResolver.class);
- if (logger.isDebugEnabled()) {
- logger.debug("Unable to locate LocaleResolver with name '" + LOCALE_RESOLVER_BEAN_NAME +
- "': using default [" + this.localeResolver + "]");
- }
- }
- }

它传入了LocaleResolver.class,这个类有多个实现类,包括AcceptHeaderLocaleResolver、CookieLocaleResolver、FixedLocaleResolver等,对应了多种不同的处理方式,你可以决定用哪一种处理方式(绑定对应的组件就好了)。但试想一下,如果用if-else来决定用那种处理方式,光一个LocaleResolver,代码就将变得又长又臭,更何况springMVC还要初始化这么多其他组件。策略模式就用了面向对象的思想,用接口、继承、多态来代替if-else,增加了代码的可读性和可维护性。
springMVC在决定request的media types时也用到了策略模式。其中的ContentNegotiationManager是最核心的一个类,其中有一个方法我们可以重点来看一下:
- @Override
- // 处理请求的mediaTypes
- public List<MediaType> resolveMediaTypes(NativeWebRequest request) throws HttpMediaTypeNotAcceptableException {
- // 遍历绑定的strategy来处理请求,其中ContentNegotiationStrategy是一个接口,它的实现类包含了多种决定mediaTypes的策略
- for (ContentNegotiationStrategy strategy : this.strategies) {
- // 如果某一策略实现类识别的请求的mediaTypes,则返回,未识别则继续遍历
- List<MediaType> mediaTypes = strategy.resolveMediaTypes(request);
- if (mediaTypes.isEmpty() || mediaTypes.equals(MEDIA_TYPE_ALL)) {
- continue;
- }
- return mediaTypes;
- }
- return Collections.emptyList();
- }
ContentNegotiationStrategy接口只有一个方法:
- public interface ContentNegotiationStrategy {
- // 返回请求的mediaTypes集合,如果无法识别则抛出异常
- List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
- throws HttpMediaTypeNotAcceptableException;
- }
跟上面的LocaleResolver接口一样,ContentNegotiationStrategy也有多种不同的实现类,这些实现类大都对应了不同的处理方式。策略模式在此情形下,使得在处理request的mediaTypes时,省去了大量if-else判断,实现了策略算法与调用逻辑上的解耦。
从上面两个简单的例子中我们可以看到,使用策略模式的实现方式是:
(1)定义策略接口->实现不同的策略类->利用多态或其他方式调用策略;
(2)从springMVC处理request的media types中,我们又可以学到:当我们遇到的问题时,如果无法事先知道哪种处理方式合适,可以使用策略模式。当某一种策略模式匹配时,返回正确结果,以此解决问题;
此外,策略模式使用的场景,实现的方式还有很多,这里就不一一赘述了。
总之,使用策略模式对我们处理和解决问题、算法(解决方式)和使用解耦、代码的可读性和可维护性方面都有极大的好处。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。