Java设计模式系列之——策略模式

前言

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

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

 

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

 

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

 

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

 

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

又或者

 

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

 

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

什么是策略模式

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

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

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

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

策略模式的三个角色

  • 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加成下,大家都能理解策略模式。最后,不知道说什么了,再来一个卢指导的表情包吧,也送给所有码之初的小伙伴。

 

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

更多的设计模式文章在我的公众号“码之初”同步更新,如有兴趣,可以关注阅读,谢谢!

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值