Java设计模式之命令模式

假设某个项目组分为需求组(Requirement Group,简称RG)、美工组(Page Group,简称PG)、代码组(Code Group,简称CG),还有一个项目经理,刚开始的时候客户(甲方)很乐意和每个组探讨,比如和需求组讨论需求,和美工讨论页面,和代码组讨论实现,告诉他们修改这里,删除这里,增加这些等等,这是一种比较常见的甲乙方合作模式,甲方深入到乙方的项目开发中,我们把这个模式用类图表示一下:

这个类图很简单,客户和三个组都有交流,我们看看代码:

Group是命令模式中的命令接收者角色(Receiver),客户命令它增加功能、减少功能、列出计划等

/**
 * @description: 项目组分成了三个组,每个组要接受增删改的命令
 */
public abstract class Group {
    
    public abstract void add();
    public abstract void delete();
    public abstract void change();
    public abstract void find(); // 客户要和某个小组沟通,必须先找到对应的小组
    public abstract void plan(); // 客户要求某小组列出执行计划

}
/**
 * @description: 需求组
 */
public class RequirementGroup extends Group {

    @Override
    public void add() {
        System.out.println("客户要求增加一项需求...");
    }

    @Override
    public void delete() {
        System.out.println("客户要求删除一项需求...");
    }

    @Override
    public void change() {
        System.out.println("客户要求修改一项需求...");
    }

    @Override
    public void find() {
        System.out.println("找到需求组...");
    }

    @Override
    public void plan() {
        System.out.println("客户要求列出需求变更计划...");
    }

}
/**
 * @description: 美工组
 */
public class PageGroup extends Group {

    @Override
    public void add() {
        System.out.println("客户要求增加一个页面...");
    }

    @Override
    public void delete() {
        System.out.println("客户要求删除一个页面...");
    }

    @Override
    public void change() {
        System.out.println("客户要求修改一个页面...");
    }

    @Override
    public void find() {
        System.out.println("找到美工组...");
    }

    @Override
    public void plan() {
        System.out.println("客户要求列出页面变更计划...");
    }

}
/**
 * @description: 代码组
 */
public class CodeGroup extends Group {

    @Override
    public void add() {
        System.out.println("客户要求增加一个功能...");
    }

    @Override
    public void delete() {
        System.out.println("客户要求删除一个功能...");
    }

    @Override
    public void change() {
        System.out.println("客户要求修改某个功能...");
    }

    @Override
    public void find() {
        System.out.println("找到代码组...");
    }

    @Override
    public void plan() {
        System.out.println("客户要求列出代码变更计划...");
    }

}

假设客户要求增加一项需求,看代码怎么写:

public class Client {
    
    public static void main(String[] args) {
        System.out.println("----------------客户要求增加一项需求----------------");
        Group rg = new RequirementGroup();
        rg.find(); // 找到需求组
        rg.add(); // 增加需求
        rg.plan(); // 列出需求变更计划
    }

}

# 运行结果:
----------------客户要求增加一项需求----------------
找到需求组...
客户要求增加一项需求...
客户要求列出需求变更计划...

过了一段时间,客户发现有个页面太多余,要求删除这个页面,代码如下:

public class Client {
    
    public static void main(String[] args) {
        System.out.println("----------------客户要求删除一个页面----------------");
        Group pg = new PageGroup();
        pg.find(); // 找到美工组
        pg.delete(); // 删除某个页面
        pg.plan(); // 列出页面变更计划
    }

}

问题来了,修改是可以的,但是每次都是叫一个组去,布置个任务,然后出计划,次次都这样,如果让你当甲方也就是客户,你烦不烦?而且这种方式很容易出错误,而且还真发生过,客户把美工叫过去了,要删除,可美工说需求是这么写的,然后客户又命令需求组过去,一次次的折腾,客户也烦躁了,于是和项目经理说:“我不管你们内部怎么安排,你就给我找个接头人,我告诉他怎么做,删除页面了,增加功能了,你们内部怎么处理我不管,我就告诉他我要干什么就成了...”,项目经理也很乐意这么做,于是我们改变一下类图:

Command抽象类:客户发给我们的命令,定义三个工作组的成员变量,供子类使用;定义一个抽象方法execute(),由子类来实现; Invoker实现类:项目接头人,setComand接受客户发给我们的命令,action()方法是执行客户的命令(方法名写成action()是与command的execute()区分开,避免混淆)

修改后的代码如下:

public abstract class Command {
    
    protected RequirementGroup rg = new RequirementGroup();
    protected PageGroup pg = new PageGroup();
    protected CodeGroup cg = new CodeGroup();
    
    // 只要一个方法,你要我做什么事情
    public abstract void execute();

}

两个具体的实现类:

public class AddRequirementCommand extends Command {

    @Override
    public void execute() {
        //找到需求组
        super.rg.find(); 
        //增加一份需求
        super.rg.add(); 
        //给出计划
        super.rg.plan();
    }

}
public class DeletePageCommand extends Command {

    @Override
    public void execute() {
        // 找到美工组
        super.pg.find(); 
        // 删除一个页面
        super.pg.delete(); 
        // 给出计划
        super.pg.plan();
    }

}

Command抽象类还可以有很多的子类,比如增加一个功能命令(AddCodeCommand),删除一份需求命令(DeleteRequirementCommand)等等,这里就不描述了,都很简单。

我们再看看接头人,就是Invoker类的实现:

public class Invoker {
    
    private Command command;

    public void setCommand(Command command) {
        this.command = command;
    }
    
    public void action() {
        this.command.execute();
    }

}

现在客户提出各种需求就简单的多了:

public class Client {
    
    public static void main(String[] args) {
        
        // 定义张三为接头人
        Invoker zhangsan = new Invoker();
        
        System.out.println("----------------客户要求增加一项需求----------------");
        // 客户下命令
        Command command = new AddRequirementCommand();
        // 接头人接受命令
        zhangsan.setCommand(command);
        // 接头人执行命令
        zhangsan.action();
    }
}

# 执行结果
----------------客户要求增加一项需求----------------
找到需求组...
客户要求增加一项需求...
客户要求列出需求变更计划...

假如客户又提出了新的需求:删除页面,那么只要在Client中把Command command = new AddRequirementCommand();换为Command command = new DeletePageCommand();即可,这就是命令模式,命令模式的通用类图如下:

在这个类图中,我们看到三个角色:

  • Receiver角色:这个就是干活的角色,命令传递到这里是应该被执行的,具体到上面我们的例子中就是Group的三个实现类;
  • Command角色:就是命令,需要执行的所有命令都这里声明;
  • Invoker角色:调用者,接收到命令,并执行命令,例子中项目经理就是这个角色;

命令模式比较简单,但是在项目中使用是非常频繁的,封装性非常好,因为它把请求方(Invoker)和执行方(Receiver)分开了,扩展性也有很好的保障。但是,命令模式也是有缺点的,如果需要扩展命令,就需要新增加Command类的子类,如果要新增的子类数量很多,那么是否使用命令模式就需要考虑了。

我们的例子还没有结束,通常来说,一个新增需求的命令,并不是通知给需求组就可以的,而是需要三个组通力合作的,那么我们在AddRequirementCommand命令中怎么调动其他组呢?很简单的,在AddRequirementCommand类的execute()方法中增加对PageGroup和CodePage的调用就成了,修改后的代码如下:

public class AddRequirementCommand extends Command {

    @Override
    public void execute() {
        // 找到需求组
        super.rg.find(); 
        // 增加一份需求
        super.rg.add(); 
        // 页面要增加
        super.pg.add();
        // 功能也要增加
        super.cg.add();
        //给出计划
        super.rg.plan();
    }

}

那么,客户提出需求后,又取消了,这个该怎么实现呢?其实取消也是一个命令,比如叫做undo(),那么在Group抽象类中增加undo()方法,3个Group继承类中重写undo()方法,然后在UndoRequirementCommand类中调用3个Group继承类中的undo()方法就可以加上这个撤销命令的功能,扩展起来还是很简单的。

本文原书: 《您的设计模式》 作者:CBF4LIFE

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏开发 & 算法杂谈

基于Happens-before的数据竞争方法汇总 (二)

Happens-before方法中最基础的方法Djit+,Djit+使用向量时钟VC进行数据竞争分析。下面这篇文章介绍的是FastTrack算法,在Djit+基...

21730
来自专栏PHP实战技术

PHP面向对象编程基本原则

首先祝大家节日快乐!!! ? 额,不知道你们剁手没,小梦是没有!整整已经错过了第九个年头! ? 小伙伴是不是有一种感觉,PHP入门的时候简直爱不释手,总是把 ...

35390
来自专栏编程

如何提高Python运行效率 超实用的四种提速方法

Python增长势头一直非常迅猛,它虽然是脚本语言,但容易学,同时,还有非常多优秀的深度学习库可用,也有越来越多的人将Python学习列入计划。Python是一...

38970
来自专栏PHP在线

从零开始学设计模式(1):基础编程模式

Introduction 俗话说,“PHP是世界上最好的语言”,因为PHP什么都能干。但是在PHP编程中,你是否会遇到这样的困惑:明明是相同的需求,但是之前写的...

39970
来自专栏Java学习网

书写高质量代码之状态维护

状态之始 我们第一眼接触新事物所触发的思考方式,决定了以后我们看待这样事物的角度,进而影响更深层次的理解和行为。 编程相对于人类历史的进程而言,不过是个六七岁孩...

30740
来自专栏云加头条

智能云上手指南:语音合成 API 快速接入

本文将为大家讲解如何上手智能云提供的智能语音识别服务。

1.1K20
来自专栏python3

python介绍

python是一门优秀的综合语言,python的宗旨是简明,优雅,强大,在人工智能,云计算,金融分析,大数据开发,web开发,自动化运维,测试等方向应用广泛,已...

34310
来自专栏软件开发 -- 分享 互助 成长

虚拟存储管理

程序局部性原理:基于大量的程序运行特征的观察发现在一段时间内,一个程序的执行往往是呈现高度的局部性。 表现在以下两个方面: 时间局部性:若一条指令被执行,那么不...

21460
来自专栏DT乱“码”

java中接口的作用

很多JAVA初级程序员对于接口存在的意义很疑惑。不知道接口到底是有什么作用,为什么要定义接口。       好像定义接口是提前做了个多余的工作。下面我给大家总结...

265100
来自专栏儿童编程

儿童编程“控制”部分学习总结

在任何编程语言中,控制部分都是非常重要的,也是体现编程语言神奇之处。在Scratch中同样如此。初次学习肯定会感觉有些抽象,但是在实际应用之中,则会体现出其功能...

13020

扫码关注云+社区

领取腾讯云代金券