012.命令模式

假设某个项目组分为需求组(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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,222评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,455评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,720评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,568评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,696评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,879评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,028评论 3 409
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,773评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,220评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,550评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,697评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,360评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,002评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,782评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,010评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,433评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,587评论 2 350

推荐阅读更多精彩内容

  • 靠孩子吗? 靠得住,孩子就不必去学校了,当孩子有强烈的学习动机,持续的学习毅力,有效的学习方法时学习才靠自己! 动...
    问学行者阅读 230评论 0 0
  • 修身养性: 王阳明说:“修心养性就像这样,好比阳光照射雾气。雾气乃混浊之物,要想升华复化为无形无相之体,就必须经受...
    月儿琬琬的梦想30阅读 332评论 0 1
  • 昨晚在手机微信里,收到了媛的妈妈发来的留言:周老师,你们班是按什么调的座位?一看到这样的信息,我就知道,麻烦事儿又...
    你是幸运的周杰阅读 400评论 0 7