0x1、定义
原始定义
定义一系列算法,封装每个算法,并使它们可以互相替换。策略模式使得算法的变化独立于使用它的客户端。
这里的算法和上节的模板模式说的"算法"一样,不特指数据结构和算法中的算法,可理解为广义上的 业务逻辑。
0x2、写个简单例子
Talk is cheap, show you the code,一个简易计算器的例子,没使用策略模式前:
public class Calculator { public static void main(String[] args) { System.out.println("计算:4 + 2 = " + compute("+", 4, 2)); System.out.println("计算:4 - 2 = " + compute("-", 4, 2)); System.out.println("计算:4 * 2 = " + compute("*", 4, 2)); System.out.println("计算:4 / 2 = " + compute("/", 4, 2)); } public static float compute(String operator, int first, int second) { float result = 0.0f; if(operator.equals("+")) { result = first + second; } else if(operator.equals("-")){ result = first - second; } else if(operator.equals("*")){ result = first * second; } else if(operator.equals("/")){ result = first / second; } return result; } }
写出上述代码IDE还是提示可以使用switch替换,不过是换汤不换药。对了,还可以把计算逻辑从compute()函数中抽离出来,独立成四个计算函数,以避免单个函数过长的问题。
接着使用策略模式重构下这个计算器,三步走,先是 策略的定义:
// 计算策略接口 public interface ICompute { String compute(int first, int second); } // 具体策略 public class AddCompute implements ICompute { @Override public String compute(int first, int second) { return "计算:" + first + " + " + second + " = " + (first + second); } } public class SubCompute implements ICompute { @Override public String compute(int first, int second) { return "计算:" + first + " - " + second + " = " + (first - second); } } public class MulCompute implements ICompute { @Override public String compute(int first, int second) { return "计算:" + first + " * " + second + " = " + (first * second); } } public class DivCompute implements ICompute { @Override public String compute(int first, int second) { return "计算:" + first + " / " + second + " = " + (first / second); } }
然后是 策略的创建和使用:
public class Context { private ICompute compute; public Context() { this.compute = new AddCompute(); } public void setCompute(ICompute compute) { this.compute = compute; } // 使用策略 public void calc(int first, int second) { System.out.println(compute.compute(first, second)); } } // 测试用例 public class TestCompute { public static void main(String[] args) { Context context = new Context(); context.setCompute(new AddCompute()); context.calc(4, 2); context.setCompute(new SubCompute()); context.calc(4, 2); context.setCompute(new MulCompute()); context.calc(4, 2); context.setCompute(new DivCompute()); context.calc(4, 2); } }
代码运行结果如下:
运行结果和初始代码一致,而且if-else不见了,是吧?但,这其实没有发挥策略模式的优势,而是退化成了:面向对象的多态 或 基于接口而非实现编程,非动态,直接在代码中指定了使用哪种策略。
而实际开发中场景更多的是:事先并不知道会使用那种策略,而是在程序运行期间,根据配置、用户输入、计算记过等不确定因素,动态地决定使用那种策略。对于上述这种 无状态的(不包含成员变量,只是纯粹的算法实现),策略对象可以共享的场景,可以把Context改写为工厂类的实现方式:
public class Context { private static final Map<String, ICompute> computes = new HashMap<>(); static { computes.put("+", new AddCompute()); computes.put("-", new SubCompute()); computes.put("*", new MulCompute()); computes.put("/", new DivCompute()); } public void calc(String operator, int first, int second) { System.out.println(computes.get(operator).compute(first, second)); } } // 修改后的测试用例 public class TestCompute { public static void main(String[] args) { Context context = new Context(); context.calc("+", 4, 2); context.calc("-", 4, 2); context.calc("*", 4, 2); context.calc("/", 4, 2); } }
重构后的代码除了复用策略对象外,依旧没有if-else语句,分支判断真的被我们去掉了吗?实际上,是被转移了,借助 "查表法"
,把判断逻辑转移到HashMap.get()里了。还有一种类似的方式,通过遍历列表返回适合的策略,同样是转移~
对于有状态的、策略对象不可共享(每次都要new)的场景,在Java中可以利用 反射
+ 注解
的技术手段来隐藏if-else,限于篇幅,不展开讲,很多开源库都有用到。
最后,同样带出UML类图、组成角色及优缺点的讲解~
- Strategy (抽象策略类) → 定义策略的共有方法,一般是接口;
- ConcreteStrategy (具体策略类) → 实现抽象策略中类定义的共有方法;
- Context (上下文信息类) → 存放和执行需要使用的具体策略类、客户端调用的逻辑;
使用场景:
- 系统需要动态地切换几种算法;
- 多重条件选择语句,想对分支判断进行隐藏,可用策略模式把行为转移到具体策略类中;
- 只希望客户端直接使用已封装好算法,而不用关心算法的具体实现细节;
优点
- 定义一系列算法实现,让算法可互相替换,提高代码扩展性和灵活性;
- 降低多重条件嵌套语句的理解难度(转移到具体策略类中);
缺点
- 调用者可自行选择使用哪种策略,但需了解每种策略的不同,且策略发生更改都需要知道;
- 策略比较庞大时,具体策略类也会剧增,增加维护成本;
- 小型的策略,不如函数式编程简洁 (匿名函数实现不同版本算法);
以上就是本节的全部内容,谢谢~