命令(Command)模式

一 前言

离开具体业务需求谈设计模式都是耍流氓,OOP的世界里出现设计模式是为了让程序更有弹性,易于扩展。
而且经过这么多年的发展,前辈们从具体的业务需求抽象出很多种的设计模式,这次我们就来探讨一下其中之命令(Command)模式,代码用例是Java语言。

二 需求场景#

假设现在有这样一个需求:我们公司接到一个订单,要求生产一个电器遥控器,要求每次按右边的ON/OFF按钮,左边对应的插槽上的电器会响应该命令,而且厂家已经提供了所有的电器的使用接口,重要的是要考虑左边的插槽对应的电器以后可能会换成其他的电器,按下最下面的撤销按钮会执行与上次命令相反的命令,比如上次ON->撤销就是OFF,遥控器如图:

遥控器.png

厂家给的电器接口如下:

// 电灯的接口
public void on(); // 开灯
public void off();// 关灯
// 电视的接口
public void onTelevision(); // 开电视
public void offTelevision();// 关电视
// 还有其他各种电器...

还有其他的电器接口,但是这些接口必定有开和关两个基本,接口的名字可能是不一样的,因为我们不可能让厂家修改接口名称。呃...毕竟顾客就是上帝!

三 一般的实现思路

按照一般的思维逻辑我们可能这么写:

// 伪代码
// 遥控器
public class RemoteController{
      // 第i个按钮的on被点击
      public void onButtonWhenPush(int index){
        1.先找到对应i的位置的电器
        2.判断对应的电器的类型,因为每个不同的电器的接口方法不是一样的,所以需要强制类型转换一下
        3.获得电器类型,执行该电器的开方法,比如电视的话执行onTelevision(),电灯执行on(),其他电器类似
       }
       // 第i个按钮的off被点击
      public void offButtonWhenPush(int index){
         // 跟onButtonWhenPush方法的实现逻辑类似
    }
}

对了,还有一个撤销按钮没有实现,我们可以定义一个变量来记录刚刚执行命令的电器,并且每个电器类都要有一个记录电器状态的boolean值,如果这样写的,厂商提供的接口就会被我们改变,这当然不是我们想要的设计.如果我们能将每次电器执行的操作封装成一个对象,那会怎么样呢?
引入今天的主角-命令模式,先看看定义:
命令模式 将请求封装成"对象",以便使用不同的请求、队列、或者日志来参数化其他对象.命令模式也支持可撤销的操作.

四 命令模式实现

电器:
/// 电灯类
public class Light {
 public void on(){
  System.out.println("Light - ON");
 }
 
 public void off(){
  System.out.println("Light - OFF");
 }
}
/// 电视类
public class Television {
 
 public void onTelevision(){
  System.out.println("Television - ON");
 }
 
 public void offTelevision(){
  System.out.println("Television - OFF");
 }
}
命令接口:
/// 命令接口
public interface Command {
 // 执行命令
 public void execute();
 // 撤销
 public void undo();
}
电灯命令类(电视类也是差不多的,厂商给的什么接口就写什么接口):
/// 开灯命令
public class LightOnCommand implements Command{
 Light light;
 
 LightOnCommand(Light light){
  this.light = light;
 }
 public void execute(){
  light.on();
 }
 public void undo(){
  light.off();
 }
}

/// 关灯命令
public class LightOffCommand implements Command{
 Light light;
 
 LightOffCommand(Light light){
  this.light = light;
 }
 public void execute(){
  light.off();
 }
 public void undo(){
  light.on();
 }
}
空命令(这也是一种设计模式):
public class NullCommand implements Command{
 public void execute(){
  System.out.println("空指令被执行");
 }
 
 public void undo(){
  System.out.println("撤销了空指令");
 }
}
遥控器:
/// 遥控器
public class RemoteController {
 public Command undoCommand; // 上一次指令,用来执行undo
 public static final int componentCount = 5;
 Command[] onCommands;
 Command[] offCommands;

 public RemoteController()
 {
  this.onCommands = new Command[componentCount];
  this.offCommands = new Command[componentCount];
  
  // 空命令也是一种设计模式
  NullCommand nc = new NullCommand();
  for(int i = 0;i < componentCount;++i){
   this.onCommands[i] = nc;
   this.offCommands[i] = nc;
  }
 }
 
 /**
  * 设置对应插槽的命令
  **/
 public void setCommand(int index,Command onCommand,Command offCommand ){
  onCommands[index] = onCommand;
  offCommands[index] = offCommand;
 }
 
 /**
  * 打开
  **/
 public void onButtonPushed(int index){
  onCommands[index].execute();
  undoCommand = onCommands[index];
 }
 
 /**
  * 关闭
  **/
 public void offButtonPushed(int index){
  offCommands[index].execute();
  undoCommand = offCommands[index];
 }
 
 /**
  * 撤销
  **/
 public void undoButtonPushed(){
  undoCommand.undo();
 }

}
现在来进行测试:

public class CommandDesignPattern {
 public static void main(String[] args)
 {
  // 电器
  Light light1 = new Light();
  Light light2 = new Light();
  
  Television tv1 = new Television();
  Television tv2 = new Television();
  
  // 开命令
  LightOnCommand lightOnCommand1 = new LightOnCommand(light1);
  LightOnCommand lightOnCommand2 = new LightOnCommand(light2);
  TelevisionOnCommand televisionOnCommand1 = new TelevisionOnCommand(tv1);
  TelevisionOnCommand televisionOnCommand2 = new TelevisionOnCommand(tv2);

  // 空命令
  NullCommand nullCommand = new NullCommand();
  // 关命令
  LightOffCommand lightOffCommand1 = new LightOffCommand(light1);
  LightOffCommand lightOffCommand2 = new LightOffCommand(light2);
  TelevisionOffCommand televisionOffCommand1 = new TelevisionOffCommand(tv1);
  TelevisionOffCommand televisionOffCommand2 = new TelevisionOffCommand(tv2);
  
  // 设置遥控器对应插槽的命令
  RemoteController remoteController = new RemoteController();
  remoteController.setCommand(0, lightOnCommand1, lightOffCommand1);
  remoteController.setCommand(1, lightOnCommand2, lightOffCommand2);
  remoteController.setCommand(2, nullCommand, nullCommand);
  remoteController.setCommand(3, televisionOnCommand1, televisionOffCommand1);
  remoteController.setCommand(4, televisionOnCommand2, televisionOffCommand2);
  
  // 测试遥控器
  remoteController.onButtonPushed(0); // 打开槽1的灯
  remoteController.offButtonPushed(0); // 关闭槽1的灯
  remoteController.onButtonPushed(3); // 打开槽4的电视
  remoteController.offButtonPushed(3); // 关闭槽4的电视

  remoteController.undoButtonPushed();// 撤销上一次操作
 }
}
执行结果:
Light - ON
Light - OFF
Television - ON
Television - OFF
Television - ON
遥控器流程图.png

参考自:<<Head First设计模式>>

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

推荐阅读更多精彩内容