设计模式之策略模式

478 阅读7分钟

策略模式

策略模式:将若干算法和策略模式抽象出来,提供统一接口,不同的算法和策略有不同的实现类,这样在客户端就可以通过注入不同的实现对象来实现算法或者策略的动态替换,这种模式的可扩展性、可维护性更高。

  • 定义

    定义一系列方法,将这些算法封装起来,提供统一接口,彼此可以互相替换。策略模式让算法独立于使用它的客户而独立变化

  • 使用场景

    1. 针对同一类型问题有多种处理方式,仅仅是具体行为有差别
    2. 需要安全地封装多种同一类型的操作时
    3. 出现同一抽象类有多个子类,而又需要使用if-else等选择具体的子类时
  • UML

1. Context:用来操作策略的上下文环境
2. Stragety:策略的统一抽象接口
3. ConcreteStragetyA、ConcreteStargetyB:具体的策略实现类

在Stragety中抽象出算法或者策略的统一接口;对具体的实现算法进行封装,实现统一接口;环境上下文中根据具体情况,实例特定算法并注入实现对象以完成具体功能。

实例说明

以交通里程金额为背景,公交车10km内1元,超过每1元可乘坐5km;地铁6公里内3元,6-12内4元,12-22内5元,其余6元;以代码实现具体逻辑:坐公交24km和地铁18km所需金额。

示例代码1
	public class PriceCalculator {
		
		// 交通类型
	    private final static int BUS = 1;
	    private final static int SUBWAY = 2;
	
	    public static void main(String args[]) {
	
	        PriceCalculator priceCalculator = new PriceCalculator();
	
			// 计算交通里程金额,传入交通类型,里程数
	        int busPrice = priceCalculator.calculatePrice(BUS, 24);
	        int subwayPrice = priceCalculator.calculatePrice(SUBWAY, 18);
			// 测试打印输出
	        System.out.println("BUS(24km): " + busPrice + "\n" + "SUBWAY(18km): " + subwayPrice);
	
	    }
	
	    /**
	     * 公交10公里1元,超过每一元可坐5公里
	     *
	     * @param km
	     * @return
	     */
	    private int busPrice(int km) {
	        if (km <= 10)
	            return 1;
	        else {
	            return ((km - 10) / 5 * 1 + 2);
	        }
	    }
	
	    /**
	     * 地铁6公里内3元,6-12内4元,12-22内5元,其余6元
	     *
	     * @param km
	     * @return
	     */
	    private int subwayPrice(int km) {
	        if (km < 6) {
	            return 3;
	        } else if (km >= 6 && km < 12) {
	            return 4;
	        } else if (km >= 12 && km < 22) {
	            return 5;
	        } else {
	            return 6;
	        }
	    }
	
		// 根据交通类型,选择合适算法求解
	    private int calculatePrice(int type, int km) {
	        switch (type) {
	            case BUS:
	                return busPrice(km);
	            case SUBWAY:
	                return subwayPrice(km);
	        }
	        return 0;
	    }
	}
  1. 上面可以看出的问题:不是单一职责。其承担了计算公交和地铁乘坐里程计算逻辑,内部通过switch判断使用哪种方式算法来求解。如果这个时候我们需要新增的士的情况,那么就需要增加的士的具体算法,同时还要在选择交通类型求解中添加判断条件,修改难度增加。

  2. 其次,如果政策更改,交通定价有变动,那么我们必须在这个类中统一修改,如此以来肯定会影响其他类,增加修改难度和可控性。

  3. 对于以上耦合度太高的情况,可以使用策略模式来应对处理。首先抽象出共有算法接口,完成算法具体实现类;环境类中根据策划给你做方式具体实例对应算法并注入实现获取结果打印。

完善代码

  1. 抽象共有算法接口CalculateStategy

     public interface CalculateStategy {
     
         /**
          * 公用接口,根据里程返回价格
          * @param km
          * @return
          */
         int calculatePrice(int km);
     }
    
  2. 具体算法实现类

     // 公交算法实现类
     public class BusStragety implements CalculateStategy {
     
         /**
          * 公交10公里1元,超过每一元可坐5公里
          *
          * @param km
          * @return
          */
         @Override
         public int calculatePrice(int km) {
     
             int extraTotal = km - 10;
     
             int extraFactor = extraTotal / 5;
     
             int fraction = extraTotal % 5;
     
             int price = 1 + extraFactor * 1;
     
             return fraction > 0 ? ++price : price;
     
         }
     }
    
     // 地铁算法实现类
     public class SubwayStagety implements CalculateStategy {
     
         /**
          * 地铁6公里内3元,6-12内4元,12-22内5元,其余6元
          *
          * @param km
          * @return
          */
         @Override
         public int calculatePrice(int km) {
             if (km < 6) {
                 return 3;
             } else if (km >= 6 && km < 12) {
                 return 4;
             } else if (km >= 12 && km < 22) {
                 return 5;
             } else {
                 return 6;
             }
         }
     }
    
  3. 环境类中根据情况实例算法并注入获取金额打印输出

     public class TranficCalculator {
     
         public static void main(String args[]) {
             // 实例环境类
             TranficCalculator tranficCalculator = new TranficCalculator();
             // 注入对应算法实例(交通)
             tranficCalculator.setStagety(new BusStragety());
             // 传入里程获取金额
             int busPrice = tranficCalculator.calculatePrice(24);
             // 注入算法实例(地铁)并求解
             tranficCalculator.setStagety(new SubwayStagety());
             int subwayPrice = tranficCalculator.calculatePrice(18);
     
             System.out.println("BUS(24km): " + busPrice + "\n"
                     + "SUBWAY(18): " + subwayPrice);
     
         }
     
         // 共有接口成员变量
         private CalculateStategy calculateStategy;
     
         // 注入具体算法实例
         private void setStagety(CalculateStategy calculateStategy) {
             this.calculateStategy = calculateStategy;
         }
     
         // 通过接口,实现算法动态调用
         private int calculatePrice(int km) {
             return calculateStategy.calculatePrice(km);
         }
     }
    
  • 上面可以看出,通过共有的接口,将具体的算法封装在各自具体实现类的内部,解耦算法之间的逻辑。同时,环境类中通过具体情况实例对应的算法实例直接求解,免去了if-else等调教判断。逻辑更加清晰,维护性更好

  • 同时,如果此时政策改动,价格波动,我们只需要需改对应具体算法实现类即可。即使增加的士 的交通方式,也十分简单,如下:

      public class TaxiStargety implements CalculateStategy {
      
          /**
           * 3公里5元;超过3公里,每公里1元;
           *
           * @param km
           * @return
           */
          @Override
          public int calculatePrice(int km) {
              if (km <= 3) {
                  return 5;
              } else {
                  int extarTotal = km - 3;
                  return (5 + extarTotal * 1);
              }
          }
      }
    
  • 在环境类中新建的士实例注入并求解14公里的金额

      // 实例环境类
      TranficCalculator tranficCalculator = new TranficCalculator();
      // 注入实例算法对象
      tranficCalculator.setStagety(new TaxiStargety());
      // 打印输出
      System.out.println("Taxi(14): "+ tranficCalculator.calculatePrice(14));
    
  • 打印输出

      BUS(24km): 4
      SUBWAY(18): 5
      Taxi(14): 16
    

总结

通过示例我们可以看出二者区别:

  1. 前者通过if-else解决交通算法选择问题,实现简单,类型层级单一,但暴露的问题非常明显,即代码臃肿,逻辑复杂,难以升级和维护,没有结构性可言

  2. 后者通过建立抽象,将不同的策略构成一个具体的策略实现,通过不同的策略实现算法替换,简化逻辑、结构的同时,增强系统的可读性、稳定性、可扩展性,这对于复杂的业务逻辑更为直观,扩展也更为方便。

策略模式和状态模式的区别与联系

区别

  • 策略模式:

    1. 策略模式一般用于单个算法的替换
    2. 客户端事先必须知道所有的可替换策略,由客户端去指定环境类需要哪个策略,注意通常都只有一个最恰当的策略(算法)被选择
    3. 策略之间是同级的,可互相动态的在运行中替换原有策略
  • 状态模式:

    1. 而状态模式的每个状态子类中需要包含环境类(Context)中的所有方法的具体实现——条件语句
    2. 通过把行为和行为对应的逻辑包装到状态类里,在环境类里消除大量的逻辑判断
    3. 不同状态的切换由继承(实现)State的状态子类去实现,当发现修改的当前对象的状态不是自己这个状态所对应的参数,则各个状态子类自己给Context类切换状态(有职责链模式思想)
    4. 且客户端不直接和状态类交互,客户端不需要了解状态!(和策略不一样),策略模式是直接依赖注入到Context类的参数进行选择策略,不存在切换状态的操作,客户端需要了解策略

联系

  1. UML类图一样,都有抽象接口
  2. 状态模式和策略模式都是为具有多种可能情形设计的模式,把不同的处理情形抽象为一个相同的接口(抽象类),符合对开闭原则,且策略模式更具有一般性
  3. 在实践中,可以用策略模式来封装几乎任何类型的规则,只要在分析过程中听到需要在不同实践应用不同的业务规则,就可以考虑使用策略模式处理,在这点上策略模式是包含状态模式的功能的

深度拓展

参考文献: Android源码设计模式解析与实战