JAVA设计模式之命令模式

一 概述

最近重读Gof的设计模式,偶尔还是会感叹此书(汉语翻译版)对初学者的不友好,不知道是不是与翻译有关。有时上面的字你都认识,但是连起来就不知道他在说啥,加上此书使用早期 C++ 作为示例代码。。。真的建议初学者不要浪费时间在这本书上,可以先找几本浅显易懂的入门,然后水平到达一定程度了再涉猎此书。。。

今天我们就浅显易懂的了解一下一个比较常用的设计模式:Command pattern

1.1 定义

将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化,对请求排队或者记录日志,可以提供命令的撤销和恢复功能。

  • 命令模式属于行为型模式
  • 我们遇到最常见的命令模式就是关机操作了,我们只需点击一下关机按钮就可以了,至于计算机是如何关机的,我们不需要关心其实现细节

1.2 使用场景

我反复强调,设个非常重要!因为纵使你有十八般武器,不知道什么时候用也是白搭。从定义上我们就可以看出其可以解决的问题。

  • 当需要将各种执行的动作抽象出来,使用时通过不同的参数来决定执行哪个对象
  • 当某个或者某些操作需要支持撤销的场景
  • 当要对操作过程记录日志,以便后期通过日志将操作过程重新做一遍时
  • 当某个操作需要支持事务操作的时候

以上是命令模式可以胜任的场景,需要你在实践中不断摸索和体会。

1.3 UML 类图

在这里插入图片描述

角色说明:

  • Command(命令角色):接口或者抽象类,定义要执行的命令
  • ConcreteCommand(具体命令角色):具体的执行命令,他们需要实现 Command 接口
  • Invoker(调用者角色):负责按照客户端的指令设置并执行命令,像命令的撤销,日志的记录等功能都要在此类中完成
  • Receiver(接收者角色):真正执行命令的角色,那些具体的命令引用它,让它完成命令的执行
  • Client(客户端角色):Client可以创建具体的命令对象,并且设置命令对象的接收者

二 实现

21世纪,人类社会的科技日新月异,狂人马斯克正在努力在2050年前将100万人类送往火星殖民。同时人类社会在其他方面也在努力创新,比如人工智能,这不最近有一家科技公司竟然制造出了高智慧性爱机器人,还支持订制,真是太贴心了。王二狗很兴奋,乘着媳妇出差的时候,偷偷订制了两款:BingBing 号与 MiMi 号。

今夜,王二狗就是整个小区那个最靓的仔,一切准备就绪,准备发车。。。二狗随后会向机器人发出各种各样的指令,这个就很适合使用命令模式。

2.1 创建一个命令接口 (Command)

/**
 * 命令接口,所有具体的命令都会实现此接口,Invoker只认识此接口
 *
 * 其实现类自己包含了可以执行自己的对象(receiver),已经执行时候需要的数据
 */
public interface Command {
    void execute();
}

2.2 构建那些可以具体完成命令的角色(Receiver)

二狗发出的那些命令,得有具体的角色来执行,这就用到订制的那两个机器人啦。

1、构建一个 BingBing 号机器人

public class FanBingBingReceiver {

    public void tackOffBra(){
        System.out.println("只见冰冰纤纤玉手,顺着双峰慢慢移到了背后,一颗,两颗...");
    }

    public void tackOffPants(){
        System.out.println("伴随着重金属的摇滚音乐,冰冰扭动着水蛇腰,双手已经移动到了三角区...");
    }
    ...
}

为了和谐,此处的 BingBing 号机器人只支持了两个功能:tackOffBra 与 tackOffPants,如果有想更进一步者,请自便。。。

2、构建一个 MiMi 号机器人

public class YangMiReceiver {
    public void hotDance(){
        System.out.println("终日以文雅示人的大幂幂尽然也有她狂野的一面,
                 只见她随着音乐疯狂的扭动,魅惑的表情更是让人想入非非,欲罢不能...");
    }
}

为了和谐,此处指定 MiMi 号机器人只会跳辣舞。

2.3 构建各种具体命令(ConcreteCommand)

来看看王二狗这厮都会发送什么猥琐的命令吧?

构建一个脱胸罩命令,其要实现 Command 接口。因为只有 BingBing 机器人提供这个功能,所以我们要在这个命令内部使用 FanBingBingReceiver 来具体执行。

public class TuoXiongZhaoCommand implements Command {
    private FanBingBingReceiver bingBing;

    public TuoXiongZhaoCommand(FanBingBingReceiver bingBing) {
        this.bingBing = bingBing;
    }

    @Override
    public void execute() {
        bingBing.tackOffBra();
    }
}

构建一个脱裤子命令,与脱胸罩命令类似

public class TuoKuZiCommand implements Command {
    private FanBingBingReceiver bingBing;

    public TuoKuZiCommand(FanBingBingReceiver bingBing) {
        this.bingBing = bingBing;
    }

    @Override
    public void execute() {
        bingBing.tackOffPants();
    }
}

二狗还想看辣舞,所以再构建一个跳辣舞命令。由于只有 MiMi 号机器人提供这个能力,所以此处的执行者就变成了 YangMiReceiver

public class TiaoLaWuCommand implements Command {
    private YangMiReceiver daMiMi;
    private String duration;//跳舞的时长

    public TiaoLaWuCommand(YangMiReceiver daMiMi, String duration) {
        this.daMiMi = daMiMi;
        this.duration = duration;
    }

    @Override
    public void execute() {
        System.out.println(String.format("开始跳舞表演,时长为:%s", duration));
        daMiMi.hotDance();
    }
}

我们看到,此命令里面除了具体执行命令的 YangMiReceiver,还包含了命令执行时需要的数据,例如跳舞时长。这也是命令模式需要注意的地方,具体的命令类里不止包含具体执行命令的那个对象,也包含相关数据。

2.4 构建命令的调用者 (Invoker)

机器人和命令都准备好了,那么具体怎么发送呢?这就是 Invoker 的角色了。二狗买这两个机器人时还带了一个智能语音控制器,他通过这个控制器就可以发出各种命令了。

public class RobotInvoker {
    private final List<Command> sexRobotCommands = new ArrayList<>();

    public void clearCommand(){
        sexRobotCommands.clear();
    }

    //设置一套命令,不知道具体执行者是谁
    public void addCommands(Command command) {
        sexRobotCommands.add(command);
    }

    //执行脱衣系列命令
    public void executeCommand() {
        for (Command tuoYiCommand : sexRobotCommands) {
            tuoYiCommand.execute();
        }
    }
}

2.5 客户端测试

终于到了发车的时刻了。二狗兴奋的唤起了控制系统(RobotInvoker )喊道:来来,让 bingbing 先把胸罩脱了,再把裤子脱了… 还有,让 mimi 来段辣舞,要那种让人欲罢不能的辣舞哦…

public class DogWang2Client {

    //享受xa机器人的服务
    public void enjoySexRobot() {
        //robot 控制系统,用户通过此系统来发出命令
        RobotInvoker invoker = new RobotInvoker();

        //生成脱衣命令
        FanBingBingReceiver bingBingReceiver = new FanBingBingReceiver();
        TuoXiongZhaoCommand tuoXiongZhaoCommand = new TuoXiongZhaoCommand(bingBingReceiver);
        TuoKuZiCommand tuoKuZiCommand = new TuoKuZiCommand(bingBingReceiver);
        //构建脱衣命令序列
        invoker.addCommands(tuoXiongZhaoCommand);
        invoker.addCommands(tuoKuZiCommand);
        //执行命令
        invoker.executeCommand();

        //生成跳舞命令
        YangMiReceiver yangMiReceiver = new YangMiReceiver();
        TiaoLaWuCommand tiaoLaWuCommand = new TiaoLaWuCommand(yangMiReceiver, "半小时");
        //构建跳舞命令
        invoker.clearCommand();
        invoker.addCommands(tiaoLaWuCommand);

        //执行命令
        invoker.executeCommand();
    }
}

输出结果:

只见冰冰纤纤玉手,顺着双峰慢慢移到了背后,一颗,两颗...
伴随着重金属的摇滚音乐,冰冰扭动着水蛇腰,双手已经移动到了三角区...
开始跳舞表演,时长为:半小时
终日以文雅示人的大幂幂尽然也有她狂野的一面,只见她随着音乐疯狂的扭动,魅惑的表情更是让人想入非非,欲罢不能...

三 总结

3.1 特点

  • Command 接口非常简单,通常只有一个 execute 方法,如果要支持撤销操作的话,再加一个 unexecute 方法
  • 每个具体的命令类内部封装了实际执行命令的那个类(Recevier),或者那些类,以及执行需要的数据
  • 每个具体命令类只完成一个请求,有多少个请求就有多少个命令
  • Invoker 类只认识接口 Command,其他的都不认识
  • 客户端类负责生成命令,并通过 Invoker 组装执行

3.2 优点

  • 将调用操作与具体执行者解耦
    你只管发出命令,至于命令由谁执行你不用关心。我们也可以随时将命令中具体执行者换掉,发出命令者是不知道的。例如你大老板交给经理一个任务,至于经理安排小张,还是小王来做,他根本就不关心。
  • 添加一个命令非常容易,扩展命令只需新增具体命令类即可,符合开放封闭原则
  • 很容易实现序列操作及实现回调系统
    你把命令加到一个列表中,迭代执行就可以实现序列操作了。 因为 Java 不能将函数作为参数,此处我们可以将命令对象当做参数,而这个对象还可执行,所以就实现了回调功能。

3.3 缺点

过多的命令会造成过多的类。

四 Android 中的源码实例分析

4.1 线程类

实际上 Thread 的使用就是一个简单的命令模式,先看下 Thread 的使用:

        new Thread(new Runnable() {
            @Override
            public void run() {
                //doSomeThing
            }
        }).start();

Thread 的 start() 方法即命令的调用者,同时 Thread 的内部会调用 Runnable 的 run(),这里 Thread 又充当了具体的命令角色,最后的 Runnable 则是接受者了,负责最后的功能处理。

4.2 Handler

另一个比较典型的常用到命令模式就是 Handler 了,这里就不贴代码了,简单分析下各个类的角色:

  • 接受者:Handler,执行消息的处理操作
  • 调用者:Looper,调用消息的的处理方法
  • 命令角色:Message,消息类
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值