命令与执行分离特性

命令模式有一个特性就是“命令”与“执行”分离。

在日常编程中,命令 与 执行 不分离的代码是很常见的,其中最典型的就是方法直接调用

1
2
3
4
5
6
void SayHello(){
Debug.Log("Say Hello");
}
void Start(){
SayHello();
}

如果想要让命令与执行分离,我们可以用消息机制:

1
2
3
4
5
6
7
8
9
10
11
public class SayHelloEvent{}
void Start()
{
SimpleEventSystem.GetEvent<SayHelloEvent>()
.Subscribe(sayHello =>{
Debug.Log("Say Hello");//执行
})
.AddTo(this);//相当于`UnregisterWhenGameObjectDestroyed(this)`

SimpleEventSystem.Publish(new SayHelloEvent());//命令
}

这里使用的是SimpleEventSystem,是比TypeEventSystem更方便的消息系统,其中的AddTo方法相当于UnregisterWhenGameObjectDestroyed

也就是消息机制本身的设计思想就是命令与执行分离,每个事件的发布只需要判个空就可以了,不需要关注监听的内容,我们可以在自定义的地方发布指定的事件。

我们再看下使用命令模式实现的命令与执行分离的代码,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public interface ICommand
{
void Execute();
}
public class SayHelloCommand : ICommand
{
public void Execute()
{
Debug.Log("Say Hello");//执行
}
}
void Start()
{
var command = new SayHelloCommand();//命令
command.Execute();

//我们也可以将消息系统和命令结合起来,消息系统只需要监听ICommand就可以了,这一行的写法比较固定
SimpleEventSystem.GetEvent<ICommand>.Subscribe(cmd => cmd.Execute()).AddTo(this);//执行

SimpleEventSystem.Publish(new SayHelloCommand());//命令
}

我们的命令只需要new一个Command并调用Execute就能执行了,但是执行的内容是在具体对象内部创建的,我们可以在自定义的地方new 这个Command。

三者之间的区别

  • 方法:命令与执行在一起,没有分离
  • Command:执行在Command内部实现(有分离)
  • 事件机制:执行在事件注册中实现(有分离)

Command的命令与执行分离的程度,介于方法与事件机制之间。

事件机制也能达到命令与执行分离的效果,但是事件机制一般至少需要通过两个对象才能完整使用。一个是执行对象(监听/注册)、一个是命令对象(发布)

而Command的命令与执行分离不同,可以在自定义的位置和时机执行指令。

空间与时间上的命令与执行分离

上面介绍的命令与执行的分离,指的是空间上命令与执行分离,也就是说命令的位置和执行的位置不同。

简单的延时

而我们只要加上自己的延时方法,就可以实现时间上的分离

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface ICommand
{
void Execute();
}

public class SayHelloCommand()
{
public void Execute() { Debug.Log("Say Hello"); }
}

void Start(){
var command = new SayHelloCommand();
//延时一秒后执行
Observable.Timer(TimeSpan.FromSeconds(1.0f))
.Subscribe( _ => command.Execute())
.AddTo(this);
}
//我们也可以用协程
IEnumrator Start()
{
var command = new SayHelloCommand();
yield new WaitForSeconds(1f);
command.Execute();
}

Observable是UniRx的概念。这里表示这个方法1秒后执行。

TimeSpan是C#内表示时间长度的单位,TimeSpan.FromSeconds(1.0f)表示时长为1秒的时间长度。它是UniRx的Timer需要的参数。

除了可以简单地延时之外,还可以做成命令队列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public interface ICommand
{
void Execute();
}

public class SayHelloCommand()
{
public void Execute() { Debug.Log("Say Hello"); }
}

Queue<ICommand> mQueue = new Queue<ICommand>();

void Start(){
mQueue.Enqueue(new SayHelloCommand());
mQueue.Enqueue(new SayHelloCommand());
}

void Update()
{
if(mQueue.Count > 0)
{
mQueue.Dequeue().Execute();//每一帧执行一个
}
}

这是非常简单的一个命令队列,这种命令队列在一些需要跨线程、跨平台通信情况下非常常见。

代码中我们可以控制每一个Command,做到每一帧执行一个Command的程度,这也是符合时间上命令与执行分离。

知道了这些后,我们可以用命令来做一个异步的消息同步,也可以写带延时的命令

小结

  • 命令与执行分离
    • 三个程度
      • 方法
        • 命令与执行是在一起的,命令调用时,方法就执行了
      • Command
        • 命令与执行是分离的,执行的逻辑是在Command内部
      • 事件机制
        • 命令与执行是分离的,但是需要至少两个对象才能完成一个完整的通信逻辑
  • 空间与时间上的分离
    • 空间上
      • 命令与执行的代码在不同的位置
    • 时间上
      • 控制执行时机在某一时刻