设计模式之一.命令模式

       写在最开始的话

       为什么要有设计模式?设计模式是用来做什么的?写了那么多年的代码,我认为用最简单通俗的话来说,设计模式是用来解耦代码,使得工程代码具有低耦合、高内聚的特性,模块化程度更高,进而提高软件模块的复用率以及扩展性,使得软件维护成本大大降低的一种技术。

       所有设计模式相关文章都分为两个个部分,第一个部分:设计模式的定义;第二个部分:如何使用设计模式,什么时候使用这种设计模式。

  1. 命令模式的定义是什么?

      命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。

      命令模式的类图:

                 

       命令模式允许将“发出请求的对象”和“接受与执行这些请求的对象”分隔开来。在上面的类图中,谁是“发出请求的对象”?Invoker。谁是“接受与执行这些请求的对象”?Receiver和ConcreateCommand。

 

       2. 如何使用设计模式,什么时候使用这种设计模式?

       命令模式是如何解耦代码以及体现代码的良好的扩展性的呢?请看上面的类图。在Command这个接口上是不是可以实现很多个ConcreateCommand? 同时,每个ConcnreateCommand对应一个Receiver。 当需要扩展代码的时候,新的ConcreateCommand只需要实现Command接口,然后再添加一个新的Receiver就行了,以前的旧代码完全不用修改。

      下面来再来看看具体的代码如何写?借用Head First设计模式里面的例子,为了只关注于设计,删减了一些代码细节。现在有一个遥控器,遥控器上有5对按钮,每一对按钮管理一种家电的开关。

        遥控器是invoker类,这个类的代码为:

     public class Invoker{

           Command[] onCommands;//开命令

           Command[] offCommands;//关命令

           public Invoker(){

                  onCommands = new Command[7];

                  offCommands = new Command[7];

            }

           public void setCommand(int slot, Command onCommand, Command offCommand){

                    onCommand[slot] = onCommand;

                    offCommand[slot] = offCommand;

           } //将执行命令的对象设置到请求对象中

           public void onButtonWasPushed(int slot){

                    onCommands[slot].execute();

           }//打开电器

            public void offButtonWasPushed(int slot){

                   offCommands[slot].execute();

            }//关掉电器

      }

        Invoker就是发出请求的对象,在Invoker中设置了需要具体执行的命令的对象Command之后,调用Command对象的execute方法,执行命令的具体内容。

  public interface Command{

           public void execute();

  }

     下面来添加具体的开关家电的功能:

   (1)添加遥控器的第一组开关功能:开关灯     

      假设现在需要控制电灯的开关,就要实现一个电灯的实体类Light和开关电灯的命令类LightOnCommand、 LightOffCommand。

 public class Light(){

       public void on();

       public void off();

}

 public class LightOnCommand implements Command{

        Light light;

 public LightOnCommand(Light light){

        this.light = light;

 }

Public void execute(){

         light.on();

    }

}

 

public class LightOffCommand implements Command{

        Light light;

        public LightOffCommand(Light light){

              this.light = light;

        }

        Public void execute(){

                light.off();

        }

}  

        最后,我们在Client类中,完整的执行“打开电灯、关闭电灯”这个命令,看看Invoker、command以及Light是如何协同工作的。

public class Client{

       public static void main(String[] args){

              Invoker invoker = new Invoker();//创建请求对象

             Light light = new Light();//创建请求接收对象             

           LightOnCommand lightOn = new LightOnCommand(light); //创建执行开灯请求对象

           LightOffCommand lightOff = new LightOffCommand(light);//创建执行关灯请求对象

           invoker.setCommand(0, lightOn,lightOff );//将执行请求的对象设置到请求对象中

           invoker.onButtonWasPushed(0);//执行开灯请求

           invoker.offButtonWasPushed(0);//执行关灯请求

       }

}

   (2)添加遥控器的第二组开关功能:开关音响

     添加这个功能的时候,我们就可以看到代码是如何在不影响旧的代码情况下,轻松的增加扩展代码的。

     public class Stereo{

           public void on();

           public void off();

           public void setCd();

           public void setDvd();

           public void setRadio();

           public void setVolume();

     }

     Public class StereoOnWithCDCommand implements Command{

            Stereo stereo;

            Public StereoOnWithCDCommand(Stereo stereo){

                   this.stereo = stereo;

             }

            Public void execute(){

                  stereo.on();

                  stereo.setCD();

                   stereo.setVolume(11);

             }

   }

  Public class StereoOffWithCDCommand implements Command{

         Stereo stereo;

         Public StereoOffWithCDCommand(Stereo stereo){

               this.stereo = stereo;

           }

          Public void execute(){

                 stereo.off();

           }

  }

   最后,如何在Client中开关音响呢?只需要把light相关的类换成stereo相关的类就可以了。

    public class Client{

            public static void main(String[] args){

                    Invoker invoker = new Invoker();//创建请求对象

                    Stereo stereo = new Stereo();//创建请求接收对象

                    StereoOnCommand stereoOn = new StereoOnCommand(stereo);//创建执行开灯请求对象

                     StereoOffCommand stereoOff = new StereoOffCommand(stereo);//创建执行关灯请求对象

                     invoker.setCommand(1, stereoOn,stereoOff );//将执行请求的对象设置到请求对象中

                     invoker.onButtonWasPushed(1);//执行开灯请求

                      invoker.offButtonWasPushed(1);//执行关灯请求

              }

      }

       在这里我们就可以看到,整个过程只添加了新的Stereo、StereoOnCommand、StereoOffCommand类,在Client中添加调用语句即可,旧有的代码完全没有受到任何影响。

      由此,我们也可以得出展开的命令模式的类图:

           

       在上面这张类图中,可以看出在“区域1”和“区域2”中,是随意增加新的类和功能的,而Invoker类以及Command接口可以保持不变,这就是命令模式的扩展性。

       另外,Client连接Light、Stereo、LighOnCommand、StereoOCommand的箭头是表示创建对象,而Invoker与Command之间的箭头以及LightOnCommand与Light之间、StereoOnCommand与Stereo之间的箭头是表示依赖关系,也就是说Invoker中有实现了Command接口的对象stereoOnCommand、LightOnCommand作为成员;StereoOnCommand中有Stereo作为成员;LightOnCommand中有Light作为成员。

      (3)什么时候使用命令模式

       命令模式除了像上面描述的那样使用外,还可以用于队列请求和日志请求。

      命令可以将运算快(一个接收者和一组动作)打包,放到一个工作队列中。想象有一个工作队列:你在某一端添加命令,然后另一端则是线程。线程从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再去除下一个命令........

        某些应用需要我们将所有的动作都记录在日志中,并能在系统死机之后,重新调用这些动作恢复到之前的状态。因此,我们只需要在command接口中增加store()、load()两个方法,然后在store()方法中存储死机前所做的操作,死机后调用load()方法将这些操作以及对象重新恢复就行了。

 

 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值