行为型模式——命令(Command)
问题背景
当遇到以下需求时,考虑使用命令:
- 将用户和实际执行请求的对象解耦。
- 支持撤销操作。
- 多种操作对应同一种请求。
- 一个请求执行一串连续操作。
命令的应用场景非常广泛,一个不得不提的就是菜单功能。考虑一个菜单,首先,用户只需要在菜单上做同样的操作(选择一个选项)就可以实现各种截然不同的功能,这说明菜单将用户和实际操作解耦了;其次,绝大多数软件的菜单都会有“撤销”选项;不同的选项也可能对应同一种操作,一个选项可能是几个选项的复合指令。菜单的需求几乎完美契合了命令模式的前提条件,于是使用命令实现菜单成为了首选方案。
解决方案
一旦需要将用户与具体实现解耦,首先应该想到的就是接口。我们想要让用户和实际执行操作的对象解耦,只需要将实际执行操作的对象进行抽象。因为这类对象的主要功能就是执行一个操作,所以我们将其抽象为“命令”。我们提取一个命令接口ICommand,包含执行操作(如果需要,也可以包含撤销操作),所有具体命令都实现这个接口。如此一来,用户在使用系统时只需要调用执行或撤销方法就能完成不同的操作。
用户只需要知道,命令对象一定会完成规定的操作,至于如何完成,Who cares?这也就意味着,命令对象的实现方式是没有约束的,我们大可以在命令对象的接口方法里直接实现功能。如果不行,还可以让命令对象仅作为一个中转,将命令传递给其他可以处理的对象去处理。如果情况更复杂,我们甚至可以玩套娃,让一个命令对象去调用其他的一串命令对象,实现功能组合(宏)。
从语义上讲,命令对象的职责只应该是执行一项操作。但是,一个菜单项可能有很多其他信息,比如所处的位置、提示文字等,这些信息不应该由命令对象储存。可以用一个菜单项对象记录这些信息,并让菜单项对象聚合一个命令对象。
根据以上的描述设计一个程序,结构如下:
如图,Close是一个简单命令,直接由命令对象实现,Save命令需要另一个对象来辅助完成,SaveClose是一个复合命令,它先执行Save命令,然后执行Close命令。MenuItem是命令的载体,同时记录了一些其他信息,用户只需要跟MenuItem打交道,甚至不知道系统内部命令类的存在。
效果
- 将抽象和实现分离,提高了程序的可扩展性。
- 隐藏了系统内部的复杂性,具有统一性。
- 能很好地支持撤销操作。
- 能组合形成复杂的命令。
相关模式
- 复合:命令可以通过复合形成更复杂的宏命令。
- 备忘录:命令配合备忘录可以很好地实现撤销功能。
实现
using System;
namespace Command
{
class Client
{
public interface ICommand
{
void Execute();
}
public class Close : ICommand
{
public void Execute()
{
Console.WriteLine("关闭");
}
}
public class Save : ICommand
{
private SaveHelper helper = new SaveHelper();
public void Execute()
{
helper.Save();
}
}
public class SaveClose : ICommand
{
private ICommand[] commands = new ICommand[] { new Save(), new Close() };
public void Execute()
{
foreach (var item in commands)
{
item.Execute();
}
}
}
public class SaveHelper
{
public void Save()
{
Console.WriteLine("保存");
}
}
public class MenuItem
{
private ICommand command;
public string desc;
public MenuItem(ICommand command, string desc)
{
this.command = command;
this.desc = desc;
}
public void Execute()
{
Console.WriteLine($"选择了选项: {desc}");
command.Execute();
}
}
static void Main(string[] args)
{
Console.WriteLine("在系统内部生成菜单...");
var close = new MenuItem(new Close(), "关闭");
var save = new MenuItem(new Save(), "保存");
var save_close = new MenuItem(new SaveClose(), "保存并关闭");
Console.WriteLine("用户使用菜单...");
close.Execute();
save.Execute();
save_close.Execute();
}
}
}