深入浅出设计模式——从球赛中悟命令模式

一、命令模式概念

    命令模式,顾名思义就是向对象发出命令使其执行指定操作。具体的说就是以命令的形式发出请求交给调用对象,随后调用对象又将命令传送给需要处理这条命令的对象并让目标对象执行该命令。

    老规矩,以球赛为例,通常一支球队在比赛中,战术是随着比赛的进行更具场上的情况需要不断的变化。那么教练发出改变战术的指令就是一条命令,战术就是中间的调用对象,而球员则是执行命令的目标对象。我们简单的想象一下,如果没有战术,教练需要对场上的11个人单独指挥,一号你要做什么,二号你号你要做什么……一场比赛下来,教练疯了。有了战术的存在,教练只需要说:马上执行第一套作战方案。然后场上的队员就心领神会,一场比赛下来,轻松惬意。

二、使用场景

    对可变的行为进行处理,实现指令与行为之间的松耦合。

三、结构

    

    从上面的UML图中我们可以看出命令模式由三个部分组成:

        1. 目标对象(Action的实现类):执行命令的对象(球员)。

        2. 调用对象(Command的实现类):命令集(战术)。

        3. 请求者:发出命令的对象。

四、实现

    说了这么多,上干货。

1. 定义目标对象

    球员在场上具有的战术动作其实是相同的,只是不同的时间每个球员需要执行的具体动作是不一样的,所以我需要一个动作包(Action接口),里面包含了所有球员的动作。

public interface Action {
    void attack();  //进攻
    void defend();  //防守
    void relax();   //保持体力
}

    是不是很简单,一共就三个动作。接下来我们开始训练球员,让他们理解这三个动作。

public class Player1 implements Action {
    @Override
    public void attack() {
        System.out.println("1号球员,进攻!");
    }

    @Override
    public void defend() {
        System.out.println("1号球员,防守!");
    }

    @Override
    public void relax() {
        System.out.println("1号球员,保持体力!");
    }
}

    好了,1号球员已经领悟到了动作要领,2,3号球员跟他差不多,我就不一一教他们了。

2. 定义调用对象

    接下来我们要完成调用对象,也就是战术训练。首先需要一个接口,这个接口包含一个执行命令的方法,到时候下令者之要调用这个执行方法,目标对象就会执行相应的方法。

public interface Command {
    void executed();    //执行方法
}

    就这么简单,然后开始布置具体的战术。首先是一号战术:

public class FirstTactics implements Command {

    private Map<Class<? extends Action>, Action> playerMap;

    public FirstTactics(Map<Class<? extends Action>, Action> playerMap) {
        this.playerMap = playerMap;
        System.out.println("开始执行第一套作战方案!");
    }

    @Override
    public void executed() {
        playerMap.forEach((s, o) -> o.attack());
    }

    一号战术接受一个map,这个map就是场上球员的集合,然后executed也就是战术执行方法是让所有球员参与进攻。然后来看看二号战术:

public class SecondTactics implements Command {

    private Map<Class<? extends Action>, Action> playerMap;

    public SecondTactics(Map<Class<? extends Action>, Action> playerMap) {
        this.playerMap = playerMap;
        System.out.println("开始执行第二套作战方案!");
    }


    @Override
    public void executed() {
        Action player1 = playerMap.get(Player1.class);
        Action player2 = playerMap.get(Player2.class);
        Action player3 = playerMap.get(Player2.class);
        player1.attack();
        player2.defend();
        player3.relax();
    }

    二号战术同样接受所有球员的集合,然后它对不同的球员下达了不同的指令。

3. 请求者

    最后我们来看一下命令模式是如何工作的。好了,教练要开始发布命令了。

public class Coach {
    public static void main(String[] args) {
//        Player1 player1 = new Player1();
//        Player2 player2 = new Player2();
//        Player3 player3 = new Player3();
//
//        player1.attack();
//        player2.attack();
//        player3.attack();
//
//        player1.attack();
//        player2.defend();
//        player3.relax();
//
//        player1.attack();
//        player2.attack();
//        player3.attack();

        Map<Class<? extends Action>, Action> playerMap = new HashMap<>();
        playerMap.put(Player1.class, new Player1());
        playerMap.put(Player2.class, new Player2());
        playerMap.put(Player3.class, new Player3());

        new FirstTactics(playerMap).executed();
        new SecondTactics(playerMap).executed();
        new FirstTactics(playerMap).executed();

    }
}

    注释中的内容是在没有使用命令模式的情况下我们要执行同样的操作所需要做的步骤。在只有3个球员的情况下我们已经可以看出它的臃肿,所以直接忽视了。

    我们首先需要把场上所有的队员都加入一个类似于指令库的map里面。然后通过向战术命令中传入球员来得到想要的执行效果。我们来运行一下。

    结果很明显了,只要我们找到对应的战术,球员就会根据战术中布置的指令去执行,这就是命令模式。

五、优点

    通过上面的对比相信大家都能很清楚的看出, 命令模式降低了系统耦合度,并且如果有新的命令(战术)或者战术发生变动,我们也可以很容易去实现。

六、局限性

    如果命令太多系统中可能会存在过多的调用对象,这可能是命令模式的唯一能找出来的不足了。当然了,还是那句话:世界上没有十全十美的模式,每个设计模式都有它适用的地方,只要我们的使用方式得当,那么装饰者模式可以帮助我们写出漂亮优雅的代码。

附源码地址:https://gitee.com/jack90john/command

------------------------------------------------------------------------------

欢迎关注我的个人公众号,推送最新文章

转载于:https://my.oschina.net/jack90john/blog/1790227

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值