命令模式(遥控器模式)

一,命令模式的实现:


      命令模式里边一般都有以下几个角色:客户端,请求者(人),命令控制器(遥控器)只能有一个,命令接口(电信号),命令实现(具体的电信号),接收者(电视)不一定必须要有接收者,可能就是一个具体的动作。

      这就好像以前电视没有遥控的时候,如果你想更换频道的话,必须跑到电视机前,手动按换台按钮,如果台很多的话,而你家的电视又比较古老,那么你就得更辛苦一点,得手动调出来其他频道。每次跑来跑去,很累噻。

      现在有了遥控器,解放了我们,我们(相当于请求者),想换台,发出换台的请求,遥控器接收到我们的请求后,对电视发出具体的命令,是换台,关机,还是开机。这样,我们把请求传给遥控器,遥控器来具体执行需要哪些命令。这样,我们就和电视机解耦了。虽然结果是一样的,都是换了台,但是过程去截然不同喽。我们和电视之间没有任何联系了。而联系只存在于遥控器和具体的命令,以及命令和电视之间了。我们人的责任,也就是客户端,我们得负责组装遥控器,也就是遥控器和命令之间的联系,以及把命令和电视之间建立联系,而不会出现我按2台,电视关机了这样的情况。让我想起了Struts。
下边是简单命令模式的实现代码实现: 

 



 1  public class Client{
 2      public static void main(String[] args){
 3         Receiver receiver = new Receiver();
 4         Command commandOne = new ConcreteCommandOne(receiver);
 5         Command commandTwo = new ConcreteCommandTwo(receiver);
 6         Invoker invoker = new Invoker(commandOne,commandTwo);
 7         invoker.actionOne();
 8         invoker.actionTwo();
 9     }
10 }

     //invoker是中央处理器,也就是遥控器。
11  public class Invoker{
12     private Command commandOne;
13     private Command commandTwo;
14      public Invoker(Command commandOne,Command commandTwo){
15         this.commandOne = commandOne;
16         this.commandTwo = commandTwo;
17     }
18      public void actionOne(){
19         commandOne.execute();
20     }
21      public void actionTwo(){
22         commandTwo.execute();
23     }
24 }
25  public interface Command{
26     void execute();
27 }
28  public class ConcreteCommandOne implements Command{
29     private Receiver receiver
30      public ConcreteCommandOne(Receiver receiver){
31         this.receiver = receiver;
32     }
33      public void execute(){
34         receiver.actionOne();
35     }
36 }
37  public class ConcreteCommandTwo implements Command{
38     private Receiver receiver
39      public ConcreteCommandTwo(Receiver receiver){
40         this.receiver = receiver;
41     }
42      public void execute(){
43         receiver.actionTwo();
44     }
45 }
46  public class Receiver{
47      public Receiver(){
48         //
49     }
50      public void actionOne(){
51         System.out.println("ActionOne has been taken.");
52     }
53      public void actionTwo(){
54         System.out.println("ActionTwo has been taken.");
55     }
56 }

二,命令模式的功能,好处,或者说为什么使用命令模式?
上边的代码是否看起来很傻呢,本来可以这样简单实现的:
 1  public class Client{
 2      public static void main(String[] args){
 3         Receiver receiver = new Receiver();
 4         receiver.actionOne();
 5         receiver.actionTwo();
 6     }
 7 }
 8  public class Receiver{
 9      public Receiver(){
10         //
11     }
12      public void actionOne(){
13         System.out.println("ActionOne has been taken.");
14     }
15      public void actionTwo(){
16         System.out.println("ActionTwo has been taken.");
17     }
18 }

看多简洁,如果是像上边如此简单的需求,这个才应该是我们的选择,但是有些情况下这样的写法不能解决的,
或者说解决起来不好,所以引入命令模式.
(1)我们须要Client和Receiver同时开发,而且在开发过程中分别须要不停重购,改名
(2)如果我们要求Redo ,Undo等功能
(3)我们须要命令不按照调用执行,而是按照执行时的情况排序,执行
(4)开发后期,我们发现必须要log哪些方法执行了,如何在尽量少更改代码的情况下实现.并且渐少重复代码
(5)在上边的情况下,我们的接受者有很多,不止一个
解决办法:
情况一,我们可以定义一个接口,让Receiver实现这个接口,Client按照接口调用。
情况二,我们可以让Receiver记住一些状态,例如执行前的自己的状态,用来undo,但自己记录自己的状态
 实现起来比较混乱,一般都是一个累记录另一个类的状态.
情况三,很难实现
情况四,,我们须要在每个Action,前后加上log
情况五,相对好实现,但是再加上这个,是否感觉最终的实现很混乱呢
好,我们再来看看命令模式,在命令模式中,我们增加一些过渡的类,这些类就是上边的命名接口和命令实现,
这样就很好的解决了情况一,情况二。我们再加入一个Invoker,这样情况三和情况四就比较好解决了。
如下加入Log和排序后的Invoker
 1  public class Invoker{
 2     private List cmdList = new ArrayList();
 3      public Invoker(){
 4     }
 5      public add(Command command){
 6         cmdList.add(command);
 7     }
 8      public remove(Command command){
 9         cmdList.remove(command);
10     }
11      public void action(){
12         Command cmd;
13          while((cmd =getCmd()) != null){
14             log("begin"+cmd.getName());
15             cmd.execute();
16             log("end"+cmd.getName());       
17         }
18     }
19      public Command getCmd(){
20         //按照自定义优先级,排序取出cmd
21     }
22 }
23  public class Client{
24      public static void main(String[] args){
25         Receiver receiver = new Receiver();
26         Command commandOne = new ConcreteCommandOne(receiver);
27         Command commandTwo = new ConcreteCommandTwo(receiver);
28         Invoker invoker = new Invoker();
29         invoker.add(commandOne);
30         invoker.add(commandTwo);
31         iinvoker.action();
32     }
33 }

三,命令模式与其它模式的配合使用:
1,看上边的Invoker的实现是否很像代理模式呢,Invoker的这种实现其实就是一种代理模式。
2,需求:有个固定命令组合会多次被执行
   解决:加入合成模式,实现方法如下,定义一个宏命令类:
 1  public class MacroCommand implements Command{
 2     private List cmdList = new ArrayList();
 3      public add(Command command){
 4         cmdList.add(command);
 5     }
 6      public remove(Command command){
 7         cmdList.remove(command);
 8     }
 9      public void execute(){
10         Command cmd;
11          for(int i=0;i<cmdList.size();i++){
12             cmd = (Command)cmdList.get(i);
13             cmd.execute();
14         }
15     }   
16 }

3,需求:须要redo undo
  解决:加入备忘录模式,一个简单的实现如下
 1  public class ConcreteCommandOne implements Command{
 2     private Receiver receiver
 3     private Receiver lastReceiver;
 4      public ConcreteCommandOne(Receiver receiver){
 5         this.receiver = receiver;
 6     }
 7      public void execute(){
 8         record();
 9         receiver.actionOne();
10     }
11      public void undo(){
12         //恢复状态
13     }
14      public void redo(){
15         lastReceiver.actionOne();
16         //
17     }
18      public record(){
19         //记录状态
20     }
21 }

4,需求:命令很多类似的地方
   解决:使用原型模式,利用clone
   这个就不写例子了。
四,命令模式的使用场合
1,须要callback的时候,例如java awt/swing/swt中的Listening的消息方式
2,须要对请求排队执行,命令的发送者和接受者有不同对的生命周期,就是命令执行的时候,可能发出命令的
Client已经不存在了
3,须要Redo Undo等函数
4,须要log每条命令
5,须要支持transaction,封装一组数据命令的时候.
五,最后再次总结一下命令模式的优点和缺点:
优点:
降低Client和命令接受者的耦合,是命令请求和命令执行的对象分割
便于修改和扩张
便于聚合多个命令
缺点:
造成出现过多的具体命令类,太多文件。

五,一个比较有意思的例子,来说明命令模式
Client        :看电视的人
Invoker     :遥控器
Command :电信号
具体命令 :遥控器上的按键对应的不同的电信号
Receiver    :电视机


 

在此写了7个java类来描述说明Command设计模式的实现方式;

  1、 Control.java        命令控制者对象类
  2、 Tv.java             命令接收者对象类
  3、 Command.java        命令接口类
  4、 CommandChannel.java 频道切换命令类
  5、 CommandOff.java     关机命令类
  6、 CommandOn.java      开机命令类
  7、 CommandTest.java    带有main方法的测试类(命令发送者)

 

===============   1、 Control.java
package command;

//命令控制者
public class Control {
  private Command onCommand, offCommand, changeChannel;

  public Control(Command on, Command off, Command channel) {
    onCommand = on;
    offCommand = off;
    changeChannel = channel;
  }

  public void turnOn() {
    onCommand.execute();
  }

  public void turnOff() {
    offCommand.execute();
  }

  public void changeChannel() {
    changeChannel.execute();
  }
}
===============   1 end

 

===============   2、 Tv.java
package command;

//命令接收者
public class Tv {

  public int currentChannel = 0;

  public void turnOn() {
    System.out.println("The televisino is on.");
  }

  public void turnOff() {
    System.out.println("The television is off.");
  }

  public void changeChannel(int channel) {
    this.currentChannel = channel;
    System.out.println("Now TV channel is " + channel);
  }
}
===============   2 end

 

===============   3、 Command.java
package command;

//命令接口
public interface Command {
  void execute();
}
===============   3 end

 

===============   4、 CommandChannel.java
package command;

//频道切换命令
public class CommandChannel implements Command {
  private Tv myTv;

  private int channel;

  public CommandChannel(Tv tv, int channel) {
    myTv = tv;
    this.channel = channel;
  }

  public void execute() {
    myTv.changeChannel(channel);
  }
}
===============   4 end

 

===============   5、 CommandOff.java
package command;

//关机命令
public class CommandOff implements Command {
  private Tv myTv;

  public CommandOff(Tv tv) {
    myTv = tv;
  }

  public void execute() {
    myTv.turnOff();
  }
}
===============   5 end

 

===============   6、 CommandOn.java
package command;

//开机命令
public class CommandOn implements Command {
  private Tv myTv;

  public CommandOn(Tv tv) {
    myTv = tv;
  }

  public void execute() {
    myTv.turnOn();
  }
}
===============   6 end

 

===============   7、 CommandTest.java
package command;

//命令发送者
public class CommandTest{
  public static void main(String[] args){
    //命令接收者
    Tv  myTv = new Tv();
    //开机命令
    CommandOn on = new CommandOn(myTv);
    //关机命令
    CommandOff off = new CommandOff(myTv);
    //频道切换命令
    CommandChannel channel = new CommandChannel(myTv, 2);
    //命令控制对象
    Control control = new Control( on, off, channel);

    //开机
    control.turnOn();
    //切换频道
    control.changeChannel();
    //关机
    control.turnOff();
  }
}
===============   7 end


  Command模式通常可应用到以下场景:
  1 Multi-level undo(多级undo操作)
    如果系统需要实现多级回退操作,这时如果所有用户的操作都以command对象的形式实现,系统可以简

    单地用stack来保存最近执行的命令,如果用户需要执行undo操作,系统只需简单地popup一个最近的

    command对象然后执行它的undo()方法既可。
 
  2 Transactional behavior(原子事务行为)
    借助command模式,可以简单地实现一个具有原子事务的行为。当一个事务失败时,往往需要回退到执

    行前的状态,可以借助command对象保存这种状态,简单地处理回退操作。

 

  3 Progress bars(状态条)
    假如系统需要按顺序执行一系列的命令操作,如果每个command对象都提供一个

    getEstimatedDuration()方法,那么系统可以简单地评估执行状态并显示出合适的状态条。

 

  4 Wizards(导航)
    通常一个使用多个wizard页面来共同完成一个简单动作。一个自然的方法是使用一个command对象来封

    装wizard过程,该command对象在第一个wizard页面显示时被创建,每个wizard页面接收用户输入并设

    置到该command对象中,当最后一个wizard页面用户按下“Finish”按钮时,可以简单地触发一个事件

    调用execute()方法执行整个动作。通过这种方法,command类不包含任何跟用户界面有关的代码,可以

    分离用户界面与具体的处理逻辑。

 

  5 GUI buttons and menu items(GUI按钮与菜单条等等)
    Swing系统里,用户可以通过工具条按钮,菜单按钮执行命令,可以用command对象来封装命令的执行。

 

  6 Thread pools(线程池)
    通常一个典型的线程池实现类可能有一个名为addTask()的public方法,用来添加一项工作任务到任务

    队列中。该任务队列中的所有任务可以用command对象来封装,通常这些command对象会实现一个通用的

    接口比如java.lang.Runnable。

 

  7 Macro recording(宏纪录)
    可以用command对象来封装用户的一个操作,这样系统可以简单通过队列保存一系列的command对象的状

    态就可以记录用户的连续操作。这样通过执行队列中的command对象,就可以完成"Play back"操作了。

 

  8 Networking
    通过网络发送command命令到其他机器上运行。

 

  9 Parallel Processing(并发处理)
    当一个调用共享某个资源并被多个线程并发处理时。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值