今天给大家说说田忌赛马的故事如有雷同, 纯属巧合! 话说在战国时期, 群雄割据, 硝烟四起, 茶余饭后还是少不了娱乐活动的, 其中赛马是最火爆的一天, 孙膑看到田忌像个死鸡似的就知道肯定赛马又输给了齐威王, 立马抓住田忌去跟齐威王再赛一场
孙膑: 小忌啊, 哥哥看着你心疼啊, 哥哥出对策帮你赢一盘如何?
田忌听到之后高兴得飞起, 瞪大了两只金鱼眼 Really? 只要能赢, 我赴汤蹈火, 以身相许又如何~
孙膑心里一万个草泥马在奔腾, 差点没噎死自己滚一边去, 我们这盘跟他 show hand! 赛马开始, 策略模式上场此处应该有 bgm 让我们红尘作伴活得潇潇洒洒 策马奔腾共享人世繁华... 呀啊呀啊, 呀啊啊啊啊啊啊~
一策略模式
定义
定义一组算法, 将每一个算法封装起来, 从而使它们可以相互切换
特点
1) 一组算法, 那就是不同的策略
2) 这组算法都实现了相同的接口或者继承相同的抽象类, 所以可以相互切换
UML
策略模式 UML 图. png
策略模式涉及到的角色有三个:
- 封装角色: 上层访问策略的入口, 它持有抽象策略角色的引用
- 抽象策略角色: 提供接口或者抽象类, 定义策略组必须拥有的方法和属性
- 具体策略角色: 实现抽象策略, 定义具体的算法逻辑
二实战
在跟齐威王比赛之前来分析下之前输掉比赛的策略, 首先来看封装角色, 代码如下:
- public class Context {
- private Strategy strategy;
- /**
- * 传进的是一个具体的策略实例
- * @param strategy
- */
- public Context(Strategy strategy) {
- this.strategy = strategy;
- }
- /**
- * 调用策略
- */
- public void contextInterface() {
- strategy.algorithmLogic();
- }
- }
Context 持有 Strategy 的引用, 并且提供了调用策略的方法, 很清晰
再来抽象策略角色, 定义了策略组的方法, 代码如下:
- public interface Strategy {
- public void algorithmLogic();
- }
输掉比赛的策略也是一种策略, 是具体策略角色类, 来看代码:
- public class ConcreteStrategyA implements Strategy{
- @Override
- public void algorithmLogic() {
- // 具体的算法逻辑 (输了比赛)
- System.out.println("第一场: 上等马 vs 上等马 第二场: 中等马 vs 中等马 第三场: 下等马 vs 下等马 赛果: 输!");
- }
- }
看到这里, 孙膑一阵无语, 惨不忍睹也得看结果的, 客户端代码如下:
- public class Client {
- public static void main(String[] args) {
- // 操控比赛, 这场要输
- Context context = new Context(new ConcreteStrategyA());
- context.contextInterface();
- }
- }
两句代码, 传入具体策略对象, 调用策略入口方法, 运行结果如下:
第一场: 上等马 vs 上等马 第二场: 中等马 vs 中等马 第三场: 下等马 vs 下等马 赛果: 输!
田忌跟孙膑说: 膑哥, 我怕!, 孙膑: 不用怕, 哥哥在!
田忌找到齐威王大王, 我们再... 再再来一盘, 输了请吃饭
瞅瞅孙膑出的策略, 一睹军事家的风采, 赢的具体策略类代码如下:
- public class ConcreteStrategyB implements Strategy{
- @Override
- public void algorithmLogic() {
- // 赢
- System.out.println("第一场: 下等马 vs 上等马 第二场: 上等马 vs 中等马 第三场: 中等马 vs 下等马 赛果: 赢!");
- }
- }
再来看客户端的代码:
- public class Client {
- public static void main(String[] args) {
- // 操控比赛, 这场要赢, 哈哈哈
- Context context = new Context(new ConcreteStrategyB());
- context.contextInterface();
- }
- }
运行结果如下:
第一场: 下等马 vs 上等马 第二场: 上等马 vs 中等马 第三场: 中等马 vs 下等马 赛果: 赢!
田忌拍烂手掌, 重要的是今天晚饭有着落了, 还要对膑哥哥以身相许的......
三策略模式的优缺点
优点
1) 良好的扩展性增加一种策略, 只要实现接口, 写上具体逻辑就可以了当旧策略不需要时, 直接剔除就行
2) 良好的封装性策略的入口封装在 Context 封装类中, 客户端只要知道使用哪种策略就传哪种策略对象就可以了
3) 避免了像简单工厂模式这样的多重条件判断
缺点
1) 客户端必须了解策略组的各个策略, 并且决定使用哪一个策略, 也就是各个策略需要暴露给客户端
2) 如果策略增多, 策略类的数量就会增加
四扩展
上面说到策略模式有一个缺点, 就是所有的策略都必须暴露出去, 让客户端自行选择策略使用现在来改善这一缺陷, 而改善这个缺陷需要跟简单工厂模式结合混编, 继续往下看
当然, 军事家孙膑也会想到这一点, 怎么可能会把自己的套路全都暴露给别人呢, 那还怎么玩是吧不过, 历史上并没有说孙膑改善了这点, 现在是我来改善这个缺陷, 哈哈哈~
策略工厂
思考一个问题, 策略暴露了, 改善就是把策略隐藏起来, 而工厂模式就有这个效果, 客户端不需要知道策略具体是什么, 只知道结果就好 OK, 那么我们可以使用工厂模式把策略当做产品生成吗? 答案是肯定的策略模式的入口就在 Context 封装类, 可以从这个角色做手脚先看代码:
- public class Context {
- private Strategy strategy;
- // 把创建策略放在封装角色内, 客户端只需要知道结果
- public void factory(String strategyType) {
- if (strategyType.equals("WIN")) {
- strategy = new ConcreteStrategyB();
- } else if (strategyType.equals("LOSE")) {
- strategy = new ConcreteStrategyA();
- }
- }
- /**
- * 调用策略
- */
- public void contextInterface() {
- strategy.algorithmLogic();
- }
- }
代码很简单, 增加了 factory 的方法, 这个方法作用就是创建策略对象这样, 客户端就不需要去理解具体的策略, 只需知道具体策略的结果就好看看客户端代码:
- public class Client {
- public static void main(String[] args) {
- Context context = new Context();
- context.factory("LOSE");
- context.contextInterface();
- }
- }
总结
注意策略模式和工厂方法模式的区别, 在前面工厂方法模式中有说到, 这里就不再阐述策略模式本身也相对比较简单, 重点在它的扩展以及其它模式的对比, 分析各自的优缺点来看看策略工厂这样的模式存在缺点吗? 很明显, 如果需要添加或者淘汰一种策略, Context 就必须修改, 这并不符合开闭原则在设计模式之禅中的提出通过策略枚举和反射机制对策略模式进行改良, 膜拜了~ 但是要添加或淘汰策略, 还是得去对枚举进行修改, 也不符合开闭原则根据自己项目情况, 选择最适合自己项目的模式下一篇是责任链模式, 欢迎继续关注, goodbye!
设计模式 Java 源码 GitHub 下载: https://github.com/jetLee92/DesignPattern
来源: https://juejin.im/entry/5abad6296fb9a028b547eaa6