真刀实枪之策略模式
- 刘备江东娶妻,赵云他容易吗?
- “在三国演义中,最佩服诸葛亮的地方不是因为他未出茅庐而三分天下的预测,也不是他在赤壁鏖战中借东风的法术,更不是他七擒孟获的策略。那是什么?是他气死周瑜,骂死王朗的气度和风范”
- 为什么说赵云不容易,原因有二:
- 做伴郎,听着挺幸福的
- 刘备的护卫,保护老大的生命不受威胁和安全撤离,即将新娘子娶回家
- 诸葛亮气周瑜-赔了夫人又折兵
- “孙权看到刘备有雄起之意,杀是不能杀了,那会惹天下人唾弃,就想个招挫他一下,那有什么办法呢?孙权有个妹妹--孙尚香,准备招刘备做女婿,然后孙权想办法把刘备软禁起来,孙权的想法还是很单纯的嘛!就是不让你刘备回西川,然后我东吴想干啥就干啥,夺荆州,吞西川也不是不可能的。东吴的想法是好的,无奈中间多了智谋无敌的诸葛亮,他早就预测到了东吴有此招数,于是刘备去东吴招亲之前,特授予伴郎赵云三个锦囊,说是按天机拆开解决棘手问题”
- 三个锦囊
- 找乔国老帮忙(也算是走后门了)
- 求吴国太放行(诉苦)
- 孙夫人断后
- 分析下:
- 这三个计策有什么相似之处,他们都是告诉赵云怎么去做,也就是在这三个计策中都有一个方法时执行,具体执行什么内容,每个计谋当然是不同的了:想到这里,便有了设计思路:三个妙计应该实现的是同一个接口
-
类图
-
代码
-
IStrategy
package com.peng.cl; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public interface IStrategy { // 每个锦囊妙计中的执行方法 public void operate(); }
-
BackDoor
package com.peng.cl; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class BackDoor implements IStrategy { @Override public void operate() { System.out.println("找乔国老帮忙,让吴国太给孙权施加压力!"); } }
-
GiveGreenLight
package com.peng.cl; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class GiveGreenLight implements IStrategy { @Override public void operate() { System.out.println("求吴国太开绿灯,放行!"); } }
-
BlockEnemy
package com.peng.cl; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class BlockEnemy implements IStrategy { @Override public void operate() { System.out.println("孙夫人断后,挡住追兵!"); } }
-
-
- 三个妙计都有了,还少两个配角
- 锦囊--存放的地方
- 赵云--执行者
-
补充一下类图
-
-
补充代码
-
Context
package com.peng.cl; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class Context { // 构造函数--使用哪一个妙计 private IStrategy strategy; public Context(IStrategy strategy) { super(); this.strategy = strategy; } // 使用计谋 public void operate() { this.strategy.operate(); } }
-
ZhaoYun
package com.peng.cl; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class ZhaoYun { // 根据诸葛亮的吩咐,一次拆开妙计 public static void main(String[] args) { Context context; // 刚到吴国拆一个 System.out.println("刚到吴国拆掉第一个"); context = new Context(new BackDoor()); context.operate(); // 刘备乐不思蜀了--拆掉第二个 System.out.println("刘备乐不思蜀了,拆掉第二个"); context = new Context(new GiveGreenLight()); context.operate(); // 孙权的兵追来了,拆开第三个锦囊 System.out.println("孙权的兵追来了,拆开第三个锦囊"); context = new Context(new BlockEnemy()); context.operate(); } }
- 执行结果
刚到吴国拆掉第一个 找乔国老帮忙,让吴国太给孙权施加压力! 刘备乐不思蜀了,拆掉第二个 求吴国太开绿灯,放行! 孙权的兵追来了,拆开第三个锦囊 孙夫人断后,挡住追兵!
-
-
策略模式的定义
- Strategy Pattern
- Define a family of algorithms,encapsulate each one,and make them interchangeable.(定义一组算法,将每个算法都封装起来,并且使他们之间可以互换)
- 通用类图
- 策略模式使用的就是面向对象的继承和多态机制,非常容易理解和掌握,再来看看策略模式的三个角色
- Context封装角色:也叫做上下文角色,起承上启下的封装作用,屏蔽高层模块对策略、算法的直接访问,封装可能存在变化
- Strategy抽象策略模式:策略,算法家族的抽象,通常为接口,定义每个策略或算法必须具有的方法和属性。【Algorithm:运算法则】
- ConcreteStrategy具体策略角色:实现抽象策略中的操作
-
通用代码
-
Strategy
package cl2; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public interface Strategy { // 策略模式的运算法则 public void doSomething(); }
-
ConcreteStrategy1
package cl2; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class ConcreteStrategy1 implements Strategy { @Override public void doSomething() { System.out.println("具体策略1的运算法则!"); } }
-
ConcreteStrategy2
package cl2; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class ConcreteStrategy2 implements Strategy { @Override public void doSomething() { System.out.println("具体策略2的运算法则!"); } }
-
Context
package cl2; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class Context { // 抽象策略 private Strategy strategy = null; // 构造函数设置具体策略 public Context(Strategy strategy) { super(); this.strategy = strategy; } // 封装后的策略方法 public void doAnything() { this.strategy.doSomething(); } }
-
Client
package cl2; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class Client { public static void main(String[] args) { // 声明一个具体的策略 Strategy strategy = new ConcreteStrategy1(); // 声明上下文对象 Context context = new Context(strategy); // 执行封装的策略 context.doAnything(); strategy = new ConcreteStrategy2(); context = new Context(strategy); // 执行封装的策略 context.doAnything(); } }
-
执行结果
具体策略1的运算法则! 具体策略2的运算法则!
- 值得细细品味
- 策略模式的重点就是封装角色,它是借用了代理模式的思路,那它和代理模式有啥区别:差别就是策略模式的封装角色和被封装的策略类不用的是同一套接口,如果是同一个接口那就成了代理模式;
- 策略模式采用了面向对象的继承和多态机制,其他没有什么玄机
-
策略模式的应用
- 策略模式的优点
- 算法可以自由切换
- 避免使用多重条件判断
- 扩展性良好:类似于一个可反复拆卸的插件(符合OCP原则:【开闭原则(Closed for Modification; Open for Extension)】)
- 策略模式的缺点
- 策略类数量类增多
- 所有的策略类都需要都需要对外暴露:可以结合工厂模式、代理模式或享元模式来修正这个缺陷
策略模式的使用场景
- 多个类只有在算法上或行为上稍有不同的场景
- 算法要求自由切换的场景
- 需要屏蔽算法规则的场景:只需传入算法的名称
策略模式的注意事项
- 如果一个系统家族的具体策略数量超过四个,则需要考虑使用混合模式,解决策略膨胀和对外暴露的问题,否则日后的系统维护就会成为一个烫手的山芋,谁都不想接
策略模式的扩展
-
先来看看实现加减法的四种方式【规则,exec(int a,int b,String symbol)方法,实现加减法--参数a和参数b为数字,参数symbol为运算规则加和减】
-
第一种【普通方法】
package com.peng.calculator; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class Calcutor { // 加符号 private final String ADD_SYMBOL = "+"; // 减符号 private final String SUB_SYMBOL = "-"; // 计算函数 public int exec(int a, int b, String symbol) { int result = 0; if (ADD_SYMBOL.equals(symbol)) { result = add(a, b); } else if (SUB_SYMBOL.equals(symbol)) { result = sub(a, b); } return result; } private int add(int a, int b) { return a + b; } private int sub(int a, int b) { return a - b; } }
-
第二种【三目运算】
package com.peng.calculator; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class Calcutor { // 加符号 private final String ADD_SYMBOL = "+"; // 减符号 private final String SUB_SYMBOL = "-"; // 计算函数 public int exec(int a, int b, String symbol) { return symbol.equals(ADD_SYMBOL) ? (a + b) : (a - b); } }
-
第三种【策略模式】
package com.peng.calculator; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public interface Calcutor { // 计算函数 public int exec(int a, int b); }
package com.peng.calculator; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class Add implements Calcutor { // 加法运算 @Override public int exec(int a, int b) { return a + b; } }
package com.peng.calculator; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class Sub implements Calcutor { // 减法运算 @Override public int exec(int a, int b) { return a - b; } }
-
第四种【策略枚举】
package com.peng.calculator; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public enum Calculator { // 加法运算 ADD("+") { public int exec(int a, int b) { return a + b; } }, // 减法运算 SUB("-") { public int exec(int a, int b) { return a - b; } }; String value = ""; // 定义成员值类型 private Calculator(String value) { this.value = value; } // 获得枚举成员的值 public String getValue() { return this.value; } // 声明一个抽象函数 public abstract int exec(int a, int b); }
- 研究一下策略枚举:先想一想它的名字,为什么叫策略枚举?枚举没问题,因为它就是Enum类型,那策略呢? 还记得其中定义了一个抽象方法exec吗!把原有定义在抽象策略中的方法移植到枚举中,每个枚举成员就成为一个具体的策略
-
那枚举策略该怎么执行呢?
package com.peng.calculator; /** * @author kungfu~peng * @data 2017年11月22日 * @description */ public class Client { public static void main(String[] args) { // 输入的两个参数是数字 System.out.println(Calculator.ADD.exec(1, 9)); System.out.println(Calculator.SUB.exec(1, 9)); } }
- 结果
10 -8
-
- 策略枚举 = 枚举 + 策略方法
最佳实践
- 致命缺陷:所有的策略都要暴露出去,这样才方便客户端决定使用哪一个策略
- 在实际项目中,一般通过工厂方法来实现策略模式类的声明
- 可以考虑混编模式
声明
- 摘自秦小波《设计模式之禅》第2版;
- 仅供学习,严禁商业用途;
- 代码手写,没有经编译器编译,有个别错误,自行根据上下文改正。