命令模式

86 阅读6分钟

命令

霓虹灯

区分参考:

www.jianshu.com/p/52d320c10…

命令是一个对象向另一个或多个对象发送的指令信息。命令的发送方负责下达指令,接收方则根据命令触发相应的行为。作为一种数据(指令信息)驱动的行为型设计模式,命令模式(Command)能够将指令信息封装成一个对象,并将此对象作为参数发送给接收方去执行,以使命令的请求方与执行方解耦,双方只通过传递各种命令过象来完成任务。此外,命令模式还支持命令的批量执行、顺序执行以及命令的反执行等操作。

既然是命令模式,那么一定要从“命令”本身切入,我们决定引入命令模块。不管是什么命令,它一定是可以被执行的,所以我们首先定义一个命令接口,以确立命令的执行规范。

命令接口Command

package Command;

public interface Command {
   void exe();//执行命令
   void unexe();//反向执行命令
}

命令接口在第4行定义了执行方法exe(),与之相反,定义了反向执行方法unexe(),之后定义的所有命令都应与此接口保持兼容,所以电灯控制系统中的开关命令类理所当然应该实现此命令接口

灯泡类Bulb

package Command;

public class Bulb {
   public void on(){
      System.out.println("灯亮");
   }
   public void off(){
      System.out.println("灯灭");
   }
}

要使灯泡亮起来就需要通电,直接用导线连接电源既不方便又很危险。既然要做的是一个电灯控制系统,那么一定要对系统采用模块化的设计理念,所以我们应该为灯泡接上一个开关。作为命令请求方,开关用来控制电源的接通与切断,所以它也应该包括2个方法:一个是按下按钮的操作,另一个是弹起按钮的操作

开关命令类SwitchCommand

package Command;

public class SwitchCommand implements Command {
   private Bulb bulb;

   public SwitchCommand(Bulb bulb) {
      this.bulb = bulb;
   }

   @Override
   public void exe() {
      bulb.on();
   }

   @Override
   public void unexe() {
      bulb.off();
   }
}

对于代码中的开关命令类SwitchCommand,由于场景比较简单,我们将所有命令简化为一个类来实现了。其实更确切的做法是将每个命令封装为一个类,也就是可以进一步将其拆分为“开命令”(OnCommand)与“关命令”(OffCommand)两个实现类,其中前者的执行方法中触发灯泡的开灯操作,反向执行方法中则触发灯泡的关灯操作,而后者则反之,以此支持更多高级功能

开关类Switcher

package Command;

public class Switcher {
   private Command command;

   //设置命令

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

   //按钮事件绑定
   public void buttonPush(){
      System.out.println("按下按钮。。。");
      command.exe();
   }

   public void buttonPop(){
      System.out.println("按下按钮");
      command.unexe();
   }
}

关类Switcher不再引入任何灯泡对象,取而代之的是第3行持有的命令接口Command,并提供了命令设置方法setCommand(),以实现命令的任意设置。之后我们在按钮操作方法中进行事件绑定,其中的按下按钮方法buttonPush()对应命令的执行方法exe(),而弹起按钮方法buttonPop()则对应命令的反向执行方法unexe()。至此,命令模块以接口以及实现类的方式被成功地植入开关控制器芯片

电灯控制系统虽然已搭建完成,但此时实现的只是灯泡的开关功能,不能完全满足用户的需求,例如用户要求实现灯泡闪烁的霓虹灯效果。当下仅有的开关命令是无法实现这种效果的。要实现这种一键自动完成的功能,我们得添加新的“闪烁”命令类

闪烁命令类FlashCommand

package Command;

public class FlashCommand implements Command {
   private Bulb bulb;
   private volatile boolean neonRun  = false;//闪烁命令状态

   public FlashCommand(Bulb bulb) {
      this.bulb = bulb;
   }

   @Override
   public void exe() {
      if (!neonRun){
         neonRun = true;
         System.out.println("霓虹灯任务启动");
         new Thread(
               ()->{
                  try {
                     while (neonRun){
                        bulb.on();
                        Thread.sleep(500);
                        bulb.off();
                        Thread.sleep(500);
                     }
                  }catch (InterruptedException e){
                     e.printStackTrace();
                  }
               }
         ).start();
      }
   }

   @Override
   public void unexe() {
      neonRun = false;
      System.out.println("霓虹灯闪耀任务结束");
   }
}

客户端类Client

package Command;

public class Client {
   public static void main(String[] args) throws InterruptedException {
      Switcher switcher = new Switcher();//命令请求方
      Bulb bulb = new Bulb();//命令执行方
      Command flashCommand = new FlashCommand(bulb);
      switcher.setCommand(flashCommand);
      switcher.buttonPush();
      Thread.sleep(3000);
      switcher.buttonPop();
   }
}

客户端对霓虹灯闪烁效果非常满意,达到了预期的效果。可以看到,在命令模式构架的电灯开关控制系统中,我们只是新添加了一个闪烁命令,并没有更改任何模块便使灯泡做出了不同的行为响应。也就是说,命令模式能使我们在不改变任何现有系统代码的情况下,实现命令功能的无限扩展。

总结:万物兼容

至此,命令模式的应用使我们的各种设备都连接了起来,要给电器设备(命令执行方)发送命令时,只需要扩展新的命令并映射至键盘(命令请求方或发送方)的某个按键(方法)。命令模式巧妙地利用了命令接口将命令请求方与命令执行方隔离开来,使发号施令者与任务执行者解耦,甚至意识不到对方接口的存在而全靠命令的上传下达。最后我们来看命令模式的类结构

Invoker(命令请求方):命令的请求方或发送方,持有命令接口的引用,并控制命令的执行或反向执行操作。对应本章例程中的控制器端,如Switcher

Command(命令接口):定义命令执行的接口标准,可包括执行与反向执行操作。

ConcreteCommand(命令实现):命令接口的实现类,可以有任意多个,其执行方法中调用命令执行方所对应的执行方法。对应本章例程中的各种命令类。

Receiver(命令执行方):最终的命令执行方,对应本章例程中的电器设备,如灯泡类Bulb

当然,任何模式都有优缺点。命令模式可能会导致系统中命令类定义泛滥的问题,读者应视具体情况而定,不要顾此失彼。命令模式其实与策略模式非常类似,只不过前者较后者多了一层封装,命令接口的统一确立,使系统可以忽略命令执行方接口的多样性与复杂性,将接口对接与业务逻辑交给具体的命令去实现,并且实现命令的无限扩展。松散的系统架构让所有模块真正实现端到端的无障碍通信,使系统兼容性获得极大的提升,万物互通、有容乃大。

Go实现模式

package command

import (
    "fmt"
    "time"
)

type Bulb struct {
}

func (b Bulb) on() {
    fmt.Println("灯亮")
}
func (b Bulb) off() {
    fmt.Println("灯灭")
}

type Command interface {
    exe()
    unexe()
}

type SwicthCommand struct {
    bulb *Bulb
}

func NewSwitchCommand(b *Bulb) *SwicthCommand {
    return &SwicthCommand{
        bulb: b,
    }
}
func (c *SwicthCommand) exe() {
    c.bulb.on()
}
func (c *SwicthCommand) unexe() {
    c.bulb.off()
}

type FlashCommand struct {
    bulb *Bulb
    neon chan int
}

func NewFlashCommand(b *Bulb) *FlashCommand {
    falsh := &FlashCommand{
        bulb: b,
        neon: make(chan int, 1),
    }
    falsh.neon <- 1
    return falsh
}

func (c *FlashCommand) exe() {
    select {
    case <-c.neon:
        go func() {
            for {
                select {
                case <-c.neon:
                    return
                default:
                    c.bulb.on()
                    time.Sleep(500 * time.Millisecond)
                    c.bulb.off()
                    time.Sleep(500 * time.Millisecond)
                }
            }
        }()
    default:
    }
}
func (c *FlashCommand) unexe() {
    c.neon <- 1
    fmt.Println("闪灯任务结束")
    c.neon <- 1
}

type Switcher struct {
    Command
}

func NewSwitcher() *Switcher {
    return &Switcher{}
}
func (s *Switcher) SetCommand(c Command) {
    s.Command = c
}

func (s *Switcher) ButtonPush() {
    fmt.Println("按下按钮。。。")
    s.Command.exe()
}
func (s *Switcher) ButtonPop() {
    fmt.Println("按下按钮")
    s.Command.unexe()
}
package command_test

import (
    command "desginPatterns/Command"
    "testing"
    "time"
)

func TestCommand(t *testing.T) {
    switcher := command.NewSwitcher()
    bulb := command.Bulb{}
    flashCommand := command.NewFlashCommand(&bulb)
    switcher.SetCommand(flashCommand)
    switcher.ButtonPush()
    time.Sleep(3000 * time.Millisecond)
    switcher.ButtonPop()
}