策略模式(Strategy)定义:该模式定义了一系列算法,并将每个算法封装起来,使它们可以相互替换,且算法的变化不会影响使用算法的客户。策略模式属于对象行为模式,它通过对算法进行封装,把使用算法的责任和算法的实现分割开来,并委派给不同的对象对这些算法进行管理。
策略模式的优点:
1、多重条件语句不易维护,而使用策略模式可以避免使用多重条件语句。
2、策略模式提供了一系列的可供重用的算法族,恰当使用继承可以把算法族的公共代码转移到父类里面,从而避免重复的代码。
3、策略模式可以提供相同行为的不同实现,客户可以根据不同时间或空间要求选择不同的。
4、策略模式提供了对开闭原则的完美支持,可以在不修改原代码的情况下,灵活增加新算法。
5、策略模式把算法的使用放到环境类中,而算法的实现移到具体策略类中,实现了二者的分离。
环境类:
1 public class Context { 2 private Strategy strategy; 3 4 public Strategy getStrategy() { 5 return strategy; 6 } 7 8 public void setStrategy(Strategy strategy) { 9 this.strategy = strategy; 10 } 11 12 public void strategyMethod() { 13 strategy.strategyMethod(); 14 } 15 }
策略类:
1 public interface Strategy { 2 public void strategyMethod(); 3 } 4 5 public class ConcreteStrategyA implements Strategy { 6 @Override 7 public void strategyMethod() { 8 // TODO Auto-generated method stub 9 System.out.println("策略A"); 10 } 11 } 12 13 public class ConcreteStrategyB implements Strategy { 14 @Override 15 public void strategyMethod() { 16 // TODO Auto-generated method stub 17 System.out.println("策略B"); 18 } 19 }
调用方式:
1 public class Client { 2 /* 3 * 例子:通用类库DBHelper 4 * 无论连接SQLServer/Oracel/MySql数据库 5 * 不同的对象,具有相同的使用方式Context 6 */ 7 public static void main(String[] args) { 8 //多个类有相似的功能,客户将具体的类传入Context,调用Context统一的方法实现具体功能 9 Strategy strategy;//扩展点 10 11 //使用策略A 12 strategy = new ConcreteStrategyA(); 13 14 //扩展点(封装了变化点)->切换策略B 15 //strategy = new ConcreteStrategyB(); 16 17 //稳定代码 18 Context context = new Context(); 19 context.setStrategy(strategy); 20 context.strategyMethod(); 21 } 22 }
执行结果:
策略模式经常用于消除多条件选择的代码,例如如下代码:
1 public class StrategyDemo { 2 public void NoStrategyMethod(Integer type) { 3 switch(type) { 4 case 1: 5 System.out.println("执行第一种策略"); 6 break; 7 case 2: 8 System.out.println("执行第二种策略"); 9 break; 10 case 3: 11 System.out.println("执行第三种策略"); 12 break; 13 default: 14 System.out.println("没有这种策略"); 15 break; 16 } 17 } 18 19 public static void main(String[] args) { 20 // TODO Auto-generated method stub 21 Integer type = 1; 22 StrategyDemo demo = new StrategyDemo(); 23 demo.NoStrategyMethod(type); 24 } 25 }
方法NoStrategyMethod(),中根据参数type的值,执行不同的逻辑,这种写法是典型的策略模式的应用场景。
如果程序的需求改变,需要增加新的type,例如type值可以为4、5、6……或者某些已有的type值得处理逻辑需要修改。
如何既能满足这些变化,又不需要修改StrategyDemo类的代码或者尽量减少对Strategy类的影响呢?
使用设计模式就是通过扩展而不是修改的方式来满足需求的变化,或者将这种修改转移到别类中。下面给出策略模式的解决方案。
首先定义以下接口和类,用于管理策略:
1 package com.example.uml; 2 3 public interface IType { 4 public void setStrategy(Strategy strategy); 5 6 public void UseStrategy(); 7 } 8 9 class MyType implements IType{ 10 11 private Strategy strategy; 12 13 @Override 14 public void setStrategy(Strategy strategy) { 15 this.strategy = strategy; 16 } 17 18 @Override 19 public void UseStrategy() { 20 this.strategy.StrategyMethod(); 21 } 22 } 23 24 25 abstract class Strategy{ 26 public abstract void StrategyMethod(); 27 } 28 29 class Strategy1 extends Strategy{ 30 31 @Override 32 public void StrategyMethod() { 33 System.out.println("执行第一种策略"); 34 } 35 } 36 37 class Strategy2 extends Strategy{ 38 39 @Override 40 public void StrategyMethod() { 41 System.out.println("执行第二种策略"); 42 System.out.println("第二种策略修改了"); 43 } 44 } 45 46 class Strategy3 extends Strategy{ 47 48 @Override 49 public void StrategyMethod() { 50 System.out.println("执行第三种策略"); 51 } 52 } 53 54 class Strategy4 extends Strategy{ 55 56 @Override 57 public void StrategyMethod() { 58 System.out.println("执行第四种策略"); 59 } 60 }
客户端调用方式发生修改:
1 package com.example.uml; 2 3 public class StrategyDemo { 4 @Deprecated 5 public void NoStrategyMethod(Integer type) { 6 switch (type) { 7 case 1: 8 System.out.println("执行第一种策略"); 9 break; 10 case 2: 11 System.out.println("执行第二种策略"); 12 break; 13 case 3: 14 System.out.println("执行第三种策略"); 15 break; 16 default: 17 System.out.println("没有这种策略"); 18 break; 19 } 20 } 21 22 public void UseStrategyMethod(IType type) { 23 type.UseStrategy(); 24 } 25 26 public static void main(String[] args) { 27 StrategyDemo demo = new StrategyDemo(); 28 29 // 不使用策略模式 30 // Integer type = 1; 31 // demo.NoStrategyMethod(type); 32 33 // System.out.println("-----------------------------------"); 34 35 // 使用策略模式 36 IType myType = new MyType(); 37 38 // 使用新增的策略4,无需修改当前类的具体业务逻辑,只需要修改new的类即可 39 Strategy strategy4 = new Strategy4(); 40 myType.setStrategy(strategy4); 41 myType.UseStrategy(); 42 43 System.out.println("-----------------------------------"); 44 45 // 假如策略2的具体逻辑发生变化,则当前类无需改变 46 Strategy strategy2 = new Strategy2(); 47 myType.setStrategy(strategy2); 48 myType.UseStrategy(); 49 } 50 }
现在的需求发生变化,第二种策略的处理逻辑改变了(增加了一行输出),并且增加了第四种的策略需要处理。
一种方案时修改第5行的方法,当然这可以完成这个需求,而且可以说是简单而直接。
但如果这个逻辑很复杂,而又经常变化,这种修改方式就有可能引入Bug,或者影响到原本正常的功能,。
因此可以实通过IType接口与Strategy抽象类,将变化点与客户端隔离。也就是将可能频繁变化的代码从当前类中抽离。
StrategyDemo类就是客户端,这个类中不再包含判断逻辑,也不包含策略的具体逻辑,只负责调用策略类的方法。
在main()方法中,只需要new出对应的实例,然后执行抽象的方法即可,具体的内容,都有MyType、Strategy1、Strategy2、Strategy3、Strategy4……这些类来处理。在新增策略或策略改变而必须修改代码时,对main()方法影响减少到最小。