Java设计模式之策略模式

在本文你能读到或者学习到:

  1. 战术大师泰伦卢的比赛策略;
  2. 设计模式之策略模式。

前言

前面讲完了设计模式的创建型模式和结构型模式,不知道一路下来有没有过陪伴的读者,因为我是三天打鱼两天晒网的模式,所以可能很久才会更新一篇,但是就这样以这种随性的方式坚持着更新,我自己觉得挺好,本身能力水平有限,文章质量可能也是层次不齐,所以还是希望大家多担待些,给我们一起成长的时间和空间。

接下来准备讲剩下的行为型模式了,因为设计模式本身可能看起来比较枯燥,每次几千字码下来,估计有耐心看的人不多,所以我想了一下,剩下的设计模式我尽量找一些生活中常见的例子,结合着实际场景讲,希望能帮助大家更容易理解,如果讲得不好,还是请见谅!

不知道有没有码之初的小伙伴看 NBA,看过 NBA 的一定会认识上图的传奇人物,没错,他就是我们今天的男猪脚——泰伦卢。人称战术大师、战术鬼才卢教头。关于他的传奇,坊间有太多的传说,感兴趣的小伙伴可以去了解一下,保证你不会失望,你会爱他恨他甚至有时候还想打他,但不管怎样,他永远那么可爱,因为他就是——

今天,我就带大家来揭秘,深入了解一下,为何大家都叫他战术鬼才,为何骑士勇士球迷都深爱着他,卢教头到底有何魅力,在短短四年间吸粉无数,成为新一代 NBA 的流量小生,对不起,流量教练。

我们先来回顾一下骑士勇士球迷都无法忘怀的 2015-16 赛季的总决赛,那一年骑士以总比分 4:3 力克勇士,成为史上第一支总决赛 1:3 落后翻盘的冠军球队,也让勇士成为史上第一支常规赛 73 胜却无缘总冠军的球队,球迷相爱相杀互撕的部分这里就不谈了,我们谈谈为何骑士那一年拿到了总冠军。是因为詹姆斯的统治力吗?还是因为欧文乐福的王者辅助?又或是因为骑士全员的众志成城?错,你们都错了,是因为他——战术鬼才卢指导。

可能没看过球的小伙伴还是一脸懵逼,说到现在说了个什么 J&B,别慌,这就为大家讲为什么赢球的功劳是卢指导的,卢指导的战术究竟如何神奇?请看卢指导的战术骚操作:

又或者

你是否领略到了战术鬼才的魅力了呢?如果你没有意识到卢指导有多么厉害,那么你可能真的不懂球。战术大师的备战计划,比赛策略都独树一帜,无论媒体什么时候采访战术问题时,卢指导都能胸有成竹的说:

看到这儿,你是否有疑虑,这真的就是我前面吹嘘的传奇教练吗?我是不是在黑他?拜托,我真的没有黑他,我还没有说完呢,既然叫战术鬼才,怎么可能就一套战术,就一套比赛策略呢?是不是?那么,卢指导的策略到底是什么呢?我们来一探究竟。

什么是策略模式

策略模式(Strategy Pattern):定义了算法家族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户,也就是算法的变化不会影响到使用算法的客户。

定义一组算法,将每一个算法封装起来,使这些算法可以相互切换。既然是一组算法,说明是不同的策略。可以相互切换,说明这些策略实现了相同的接口或者继承了相同的抽象类。

通俗点理解就是,解决一个问题,有多种办法策略。就像你追女孩子,通常的策略就是请吃饭、看电影、送花、嘘寒问暖等,这些就是不同的策略,都实现追女孩子这个方法,你想先送花还是先请吃饭,都由你自己决定,也可以随意切换。

那我们可爱的卢指导呢?我们都知道 NBA 一般是四节比赛,除非进加时赛,每场都要面对不同的对手,每一节比赛场上形势都瞬息万变,这就需要我们的卢指导日夜操劳,随时制定不同的比赛计划和策略去应对,这是一样的道理。

策略模式的 UML 图

策略模式的三个角色
  • Strategy(抽象策略角色):通常由一个接口或抽象类实现。此角色定义了所有的具体策略类的公共接口。

  • ConcreteStrategy(具体策略角色): 实现了抽象策略类,包装了相关的算法或行为。

  • Context(上下文角色): 上层访问策略的入口,它持有抽象策略角色 Strategy 的引用。

假如一共有四节比赛,每节比赛都需要一个比赛策略,也就是战术,那我们就可以抽象出一个比赛策略的接口,里面有个制定战术的方法。每一节的比赛策略就是具体策略角色,都实现了制定战术的方法。那么有同学不禁要问了,卢指导在策略模式里充当什么角色呢?没错,卢指导就是 Context 上下文角色,球员上场都需要先问卢指导打什么战术,执行哪一个计划策略,卢指导就给他们相应的比赛策略。下面我们用代码来实现一下策略模式,哦,抱歉,用代码来看一下卢指导到底是个什么样的战术鬼才。

卢指导的战术演示(策略模式代码实例)

1、编写抽象策略接口

package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能: Strategy 抽象策略角色类:比赛策略接口,也可以是抽象类 * </p> * * @author Moore * @ClassName Game plan. * @Version V1.0. * @date 2019.09.09 10:26:05 */public interface GameStrategy {    /**     * 制定比赛计划     */    void plan();}

2、编写具体策略类,实现抽象策略接口

package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能: 具体策略角色 * </p> * * @author Moore * @ClassName First game strategy. * @Version V1.0. * @date 2019.09.09 17:58:14 */public class FirstGameStrategy implements GameStrategy {    /**     * 制定比赛计划     */    @Override    public void plan() {        System.err.println("比赛计划是:把球给乐福单打,如果拉不开分差就把球给詹姆斯,詹姆斯快想想办法!");    }}
package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能: 具体策略角色 * </p> * * @author Moore * @ClassName First game strategy. * @Version V1.0. * @date 2019.09.09 17:58:14 */public class SecondGameStrategy implements GameStrategy {    /**     * 制定比赛计划     */    @Override    public void plan() {        System.err.println("比赛计划是:把球给欧文单打,如果比分落后就把球给詹姆斯,詹姆斯快想想办法!");    }}
package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能: 具体策略角色 * </p> * * @author Moore * @ClassName First game strategy. * @Version V1.0. * @date 2019.09.09 17:58:14 */public class ThirdGameStrategy implements GameStrategy {    /**     * 制定比赛计划     */    @Override    public void plan() {        System.err.println("比赛计划是:把球给 JR 和姐夫格林投三分,如果都投不进把球给詹姆斯,詹姆斯快想想办法!");    }}
package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能: 具体策略角色 * </p> * * @author Moore * @ClassName First game strategy. * @Version V1.0. * @date 2019.09.09 17:58:14 */public class LastTimeGameStrategy implements GameStrategy {    /**     * 制定比赛计划     */    @Override    public void plan() {        System.err.println("比赛计划是:把球给詹姆斯,詹姆斯快想想办法!");    }}

3、战术大师卢指导登场,编写 Context 上下文类:

package com.weiya.mazhichu.designpatterns.strategy;import lombok.Getter;import lombok.Setter;/** * <p class="detail"> * 功能:Context 角色,持有 Strategy 角色的引用 * </p> * * @author Moore * @ClassName Tyronn lue. * @Version V1.0. * @date 2019.09.10 10:09:56 */public class TyronnLue {    @Setter    private GameStrategy strategy;    public TyronnLue(GameStrategy strategy) {        this.strategy = strategy;    }    /**     * <p class="detail">     * 功能:     * </p>     *     * @author Moore     * @date 2019.09.10 10:09:56     */    public void play(){        strategy.plan();    }}

4、客户端调用(带大家回顾一下 2015-16 赛季 NBA 总决赛第七场):

package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能:总决赛,客户端引用 * </p> * * @author Moore * @ClassName Finals. * @Version V1.0. * @date 2019.09.10 10:12:17 */public class Finals {    /**     * 主程序入口.     *     * @param args :     * @author Moore     * @date 2019.09.10 10:12:17     */    public static void main(String[] args) {        System.out.println("---------2015-2016 赛季总决赛骑士 VS 勇士比赛开始!---------"+"\n\n");        System.out.println("------战术鬼才卢指导制定了 4 节比赛计划,并一一告诉了球员----------");        System.out.println("---------第一节比赛开始,骑士球员拿出第一节的比赛计划---------");        GameStrategy first = new FirstGameStrategy();        TyronnLue strategy = new TyronnLue(first);        strategy.play();        System.out.println("---------第一节比赛结束,骑士 23:22 领先-------"+"\n\n");        System.out.println("---------第二节开始,骑士球员拿出第二节比赛计划---------");        GameStrategy second = new SecondGameStrategy();        strategy.setStrategy(second);        strategy.play();        System.out.println("---------半场结束,勇士 49:42 领先骑士 7 分----------"+"\n\n");        System.out.println("---------第三节开始,骑士球员拿出第三节比赛计划---------");        GameStrategy third = new ThirdGameStrategy();        strategy.setStrategy(third);        strategy.play();        System.out.println("---------第三节结束,骑士将比分追至 75:76----------"+"\n\n");        System.out.println("---------第四节比赛双方比分交替领先,不知不觉来到了最后三分钟,两队战平---------");        System.out.println("---------骑士请求暂停,球员拿出关键时刻比赛计划---------");        GameStrategy last = new LastTimeGameStrategy();        strategy.setStrategy(last);        strategy.play();        System.out.println("---------欧文在比赛时间还剩 53 秒时命中关键三分,帮助骑士领先 3 分---------");        System.out.println("---------库里 3 分不中,欧文上篮不中,詹姆斯追冒伊戈达拉,詹姆斯两罚一中,锁定胜局!---------");        System.out.println("---------最终骑士队 93-89 力克勇士,以总比分 4-3 险胜夺冠,他们成为史上第一支在总决赛实现 1-3 落后翻盘的球队,获得 2015-16 赛季总冠军----------");        System.out.println("---------詹姆斯抱着乐福哭了,战术大师泰伦卢也哭了!---------");    }}

运行结果,看一下整场比赛,卢指导是怎样制定策略,带领詹欧乐体验冠军经历的:

至此,大家应该知道了卢指导的战术鬼才,也知道了什么是策略模式,那么策略模式有什么优缺点呢?

策略模式优点
  • 算法可以自由切换(每节比赛的比赛计划都可以随意切换)。

  • 避免使用多重条件判断。

  • 扩展性好(如果要加新的算法,只要实现抽象策略类就好了,假如进了加时赛,卢指导就要制定新的比赛策略就好了)。

策略模式缺点
  • 客户端必须知道所有的类,并且自己决定使用哪个类,也就是所有策略类都要暴露给客户端,对客户端也是个负担(球员必须一开始就知道所有的比赛策略,但是篮球场形式瞬息万变,战术太多,球员要一下子记得所有的战术太不容易)。

  • 策略类会很多。

策略模式+简单工厂

你以为故事就结束了吗?我们可爱的卢指导既然已经知道这种比赛策略有缺点,而且已经有媒体和骑士内部球员开始说闲话,说他根本就没有什么战术,就知道明牌托管,所有的战术最后都是詹姆斯快想想办法,一个总冠军还是抱詹姆斯大腿得的,随便换哪个教练都行,这下可伤到了卢指导的自尊了,于是他决定再也不明牌给球员和媒体了,他要像波波维奇那样,成为真正的受人尊敬爱戴的战术大师,那该怎么办呢?

卢指导回家想了一个休赛期,决定不再明牌,也不一开始就告诉球员所有的战术,不让球员自主选择战术了,要让球员主动来问自己战术,要树立自己的教练威望。于是卢教练第二天就执行了该想法,我们来看看卢指导怎么改的:

修改 Context 上下文类,由卢指导根据球员请求,卢指导动态决定给他们哪一项比赛策略,球员只需要上场打比赛执行给到的比赛计划就行了,不用一开始就知道整个赛季所有的具体比赛计划(具体策略类),代码如下:

1、编写 Context 上下文,利用简单工厂模式:

package com.weiya.mazhichu.designpatterns.strategy;import lombok.Setter;/** * <p class="detail"> * 功能:Context 角色,持有 Strategy 角色的引用 * </p> * * @author Moore * @ClassName Tyronn lue. * @Version V1.0. * @date 2019.09.10 10:09:56 */public class TyronnLueContext {    private GameStrategy strategy;    /**     * <p class="detail">     * 功能:     * </p>     *     * @author Moore     * @date 2019.09.10 10:09:56     */    public void play(String time){        switch (time) {            case "first":                strategy = new FirstGameStrategy();                break;            case "second":                strategy = new ThirdGameStrategy();                break;            case "third":                System.err.println("欧文去了凯尔特人了,还是执行上一节的比赛策略吧");                strategy = new ThirdGameStrategy();                break;            case "last":                strategy = new LastTimeGameStrategy();                break;        }        strategy.plan();    }}

就这样一个赛季过去了,卢教练都靠这种方式执行比赛计划,但是质疑声还是不绝于耳,卢教练心想:卢瑟,等我再带这帮崽子拿到总冠军,你们就知道我的厉害了,到时候都得乖乖的叫我诸葛-波波-泰伦卢了,嘿嘿。卢指导想到这,嘴角不自觉的上扬起来。转眼间,2017-2018 赛季总决赛来了,詹姆斯带领骑士艰难的再次站上总决赛的舞台,面对老对手勇士。下面看看两队的比赛过程,以及卢指导的神策略。

2、客户端调用

package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能: 2018 总决赛,客户端引用 * </p> * * @author Moore * @ClassName Finals. * @Version V1.0. * @date 2019.09.10 10:12:17 */public class Finals2018 {    /**     * 主程序入口.     *     * @param args :     * @author Moore     * @date 2019.09.10 10:12:17     */    public static void main(String[] args) {        System.out.println("---------2017-2018 赛季总决赛骑士 VS 勇士第一场比赛正式开始!---------"+"\n\n");        System.out.println("------战术鬼才卢指导制定了比赛计划,让球员来询问自己的时候,再告诉他们怎么执行----------");        System.out.println("---------第一节比赛开始,骑士球员请求第一节的比赛计划---------");        TyronnLueContext strategy = new TyronnLueContext();        strategy.play("first");        System.out.println("---------第一节比赛结束-------"+"\n\n");        System.out.println("---------第二节开始,骑士球员拿出第二节比赛计划---------");        TyronnLueContext strategy2 = new TyronnLueContext();        strategy2.play("second");        System.out.println("---------半场结束----------"+"\n\n");        System.out.println("---------第三节开始,骑士球员拿出第三节比赛计划---------");        TyronnLueContext strategy3 = new TyronnLueContext();        strategy3.play("third");        System.out.println("---------第三节结束----------"+"\n\n");        System.out.println("---------第四节比赛骑士一直领先,最后时刻勇士追平比分---------");        System.out.println("--------最后 9 秒,JR 抢到篮板,詹姆斯想问问卢指导有没有暂停,想请求比赛计划----------");        TyronnLueContext strategy4 = new TyronnLueContext();        System.err.println("原本詹姆斯想让 JR 请求的关键时刻的战术是--->");        strategy4.play("last");        System.out.println("---------但是卢指导懵逼中,不知道有没有暂停,也忘记返回比赛策略---------");        System.out.println("---------JR 也懵逼了,抱着球思考人生,时间走完,勇士将比赛拖进加时赛---------");        System.out.println("---------詹姆斯心灰意冷砍下 50 分,勇士士气正旺,4:0 横扫骑士获得 2017-2018 赛季总冠军!----------");    }}

运行,查看结果:

至此,我们又一次见到了卢教练的神操作,虽然改善了策略模式,但是因为自己和球员有时候神经短路,最终骑士输掉了总冠军。詹姆斯远走洛杉矶,卢指导也解甲归田,江湖从此只剩下他的传说了。

我们先不关心改变策略带来的结果,我们就只看一下策略模式结合简单工厂模式带来的优缺点。

优点

  • 策略模式与简单工厂结合的用法,客户端就只需要认识一个 Context 上下文工厂类就可以了。耦合更加降低,达到解耦目的。

  • 抽象策略的方法和客户端分离,客户端不用关心具体策略怎么制定算法的,只要通过上下文对象就可以拿到算法结果。

缺点

  • 每次增加算法,除了需要实现抽象策略接口,还要修改 Context 上下文工厂类,违背了“开闭原则”。

  • Context 上下文类中 if/else 或则 swith/case 判断条件太多,不易维护。

策略模式+简单工厂+反射

最近听说卢指导要复出了,目的地是洛杉矶快船队,和鸡汤教父里弗斯相聚,双份鸡汤战术大师相聚,加上卡哇伊乔治和一众狠角色,新赛季势必势如破竹,隔壁湖人队更衣室某老汉凶多吉少。

这儿要说的是,听说卢指导休息的这一年可没有白闲着,在家闭门修炼策略模式,他觉得 18 赛季的比赛策略不是很好,虽然球员和媒体不知道他变幻无常的战术了,但是他自己作为上下文类,里面有太多的 if/else 和 swith/case 条件判断,一个赛季 82 场比赛,要写那么多条件,太为难本卢宝宝了。

于是,卢宝宝又想了个好办法,他决定自己不去管理这么多具体策略了,每次只要有新增的策略,就交给另一个助教去保管,这样每次球员请求比赛策略的时候,他只要从助教那儿拿过来就好了。想到这,卢宝宝又开心的笑起来了,心想自己真是个战术天才,嘿嘿。

那我们就来看看卢指导是怎样改变他的策略的。

1、由助教管理所有的具体策略,这儿用枚举类

package com.weiya.mazhichu.designpatterns.strategy;import lombok.Getter;import lombok.Setter;/** * <p class="detail"> * 功能: 所有具体策略类放枚举里 * </p> * * @author Moore * @ClassName Concrete strategy enum. * @Version V1.0. * @date 2019.09.11 09:59:40 */public enum ConcreteStrategyEnum {    FIRST("com.weiya.mazhichu.designpattern.strategy.FirstGameStrategy"),    SECOND("com.weiya.mazhichu.designpattern.strategy.SecondGameStrategy"),    THIRD("com.weiya.mazhichu.designpattern.strategy.ThirdGameStrategy"),    LAST("com.weiya.mazhichu.designpattern.strategy.LastGameStrategy"),    ;    @Getter    @Setter    private String className;    ConcreteStrategyEnum(String className) {        this.className = className;    }}

2、卢宝宝自己不用那么多条件判断了,改用反射获取,修改 Context 上下文类

package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能:Context 角色,持有 Strategy 角色的引用 * </p> * * @author Moore * @ClassName Tyronn lue. * @Version V1.0. * @date 2019.09.10 10:09:56 */public class TyronnLueReflectContext {    private GameStrategy strategy;    /**     * <p class="detail">     * 功能: 利用反射动态获取具体策略类     * </p>     *     * @param time :     * @throws Exception the exception     * @author Moore     * @date 2019.09.11 10:02:20     */    public static GameStrategy getStrategy(String time) throws Exception {        String className = ConcreteStrategyEnum.valueOf(time).getClassName();        return (GameStrategy) Class.forName(className).newInstance();    }}

3、新赛季还未开始,又因卢教练对 16 赛季的骑士还有感情,所以就用老队员模拟了球员请求比赛策略的场景

package com.weiya.mazhichu.designpatterns.strategy;/** * <p class="detail"> * 功能:卢指导模拟客户端请求结合工厂模式+反射的策略模式 * </p> * * @author Moore * @ClassName Nba 2019. * @Version V1.0. * @date 2019.09.11 10:15:29 */public class NBA2019 {    public static void main(String[] args) throws Exception {        System.out.println("---------2019-20 赛季揭幕战---------"+"\n\n");        System.out.println("------战术鬼才卢指导制定了比赛计划,让球员来询问自己的时候,再告诉他们怎么执行----------"+"\n\n");        System.out.println("---------第一节比赛开始,骑士球员请求第一节的比赛计划---------");        GameStrategy strategy = TyronnLueReflectContext.getStrategy("FIRST");        strategy.plan();        System.out.println("---------第一节比赛结束-------"+"\n\n");        System.out.println("---------第二节开始,骑士球员请求第二节比赛计划---------");        strategy = TyronnLueReflectContext.getStrategy("SECOND");        strategy.plan();        System.out.println("---------半场结束----------"+"\n\n");        System.out.println("---------第三节开始,骑士球员请求第三节比赛计划---------");        strategy = TyronnLueReflectContext.getStrategy("THIRD");        strategy.plan();        System.out.println("---------第三节结束----------"+"\n\n");        System.out.println("---------第四节双方交替领先,比赛来到最后两分钟,骑士球员请求关键时刻战术---------");        strategy = TyronnLueReflectContext.getStrategy("LAST");        strategy.plan();        System.out.println("---------比赛结束,詹姆斯 35+9+8 准三双率队险胜快船,取得开门红!----------");    }}

运行,看看卢指导的模拟结果:

我不知道卢宝宝新赛季会展现什么样的神操作,但至少他带我们一步步深入理解了策略模式,我们看一下结合工厂模式+反射的策略模式,做了哪些改进,有什么好处。

优点

  • 去除了 Context 中的条件判断,使代码易于读懂和维护。

  • 扩展性好,增加算法只需要增加对应的策略实现类和枚举,符合开闭原则。

好了,策略模式就讲到这儿了,希望在卢指导的 buff 加成下,大家都能理解策略模式。最后,不知道说什么了,再来一个卢指导的表情包吧,也送给所有码之初的小伙伴。

我不能保证我写的文章都是正确的,但是我能保证都是我自己花时间用心写的,所有的代码示例都是原创,所有的理解都只是我个人理解,不能代表官方权威,所以请各位读者阅读时带着批判的眼光,有选择性的认同,谢谢!

如果觉得本文有用,请推荐给您身边的人或者同行关注我的个人公众号“码之初”,专注 Java 基础、架构设计、源码阅读,读书和生活。谢谢!

如果发现文章中有问题或者代码里有 bug,欢迎留言,请随时批评指正,谢谢!

阅读全文: http://gitbook.cn/gitchat/activity/5e528ba6abb3244dfe14ace9

您还可以下载 CSDN 旗下精品原创内容社区 GitChat App ,阅读更多 GitChat 专享技术内容哦。

FtooAtPSkEJwnW-9xkCLqSTRpBKX

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值