定义
将来自客户端的请求传入一个对象,从而使你可用不同的请求对客户进行参数化。用于“行为请求者”与“行为实现者”解耦,可实现二者之间的松耦合,以便适应变化。分离变化与不变的因素。
理解
将一个请求(调用)封装为一个对象,从而可用不同的请求对客户进行参数化,将“发出请求的对象”和“接收和执行请求的对象”分离开。它的精髓在于封装了具体的调用。
我们从定义中了解到,一个命令对象通过在特定命令接受者上绑定一组动作来封装一个请求。要达到这一点,命令对象将动作和动作的接收者封装在对象中,这个对象只暴露一个execute方法,此方法被调用时,接收者就会执行这些动作。从外部来看,
其他对象(包括调用者)并不知道哪些接收者进行了哪些动作,只知道只要调用execute方法,请求的目的就会达到。
UML类图

角色
(1)抽象命令角色(Command):抽象命令,包含命令执行的抽象方法
(2)命令接收者(Receiver):命令接收者角色,它包含所有命令的具体行为实现方法。
(3)具体命令角色(ConcreteCommand):它包含一个命令接收者对象,并调用接收者的对象相应实现方法。
(4)命令调用者角色(Invoker):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
(5)客户端(Client):创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
Java代码实现
Commend:
public interface Command{
//执行命令
public void execute();
//撤销命令
public void undo();
}
Recevier
public class Receiver(){
//实现执行业务方法
public void action(){
}
//实现撤销业务方法
public void unAction(){
}
}
ConcreteCommand
public class ConcreteCommand implements Command{
Receiver receiver;
public ConcreteCommand(Receiver receiver){
this.receiver = receiver;
}
public void execute(){
receiver.action;
}
publc void undo(){
recevier.unAction();
}
}
Invoker
public class Invoker {
/**
* 调用者持有命令对象
*/
private Command command;
/**
* 设置命令对象
* @param command
*/
public void setCommand(Command command) {
this.command = command;
}
public Command getCommand() {
return command;
}
/**
* 执行命令
*/
public void runCommand(){
command.execute();
}
/**
* 撤销命令
*/
public void unDoCommand(){
command.undo();
}
}
Client
public class Client {
public static void main(String[] args){
//创建接受者
Receiver receiver = new Receiver();
//创建命令对象,并设置它的接受者
Command command = new CreateCommand(receiver);
//可直接通过命令的调用执行操作
command.execute()
//或创建调用者,将命令对象设置进去
Invoker invoker = new Invoker();
invoker.setCommand(command);
//这里可以测试一下
invoker.runCommand();
invoker.unDoCommand();
}
}
应用实例
//执行命令的接口
public interface Command {
void execute();
}
//命令接收者Receiver
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);
}
}
//开机命令ConcreteCommand
public class CommandOn implements Command {
private Tv myTv;
public CommandOn(Tv tv) {
myTv = tv;
}
public void execute() {
myTv.turnOn();
}
}
//关机命令ConcreteCommand
public class CommandOff implements Command {
private Tv myTv;
public CommandOff(Tv tv) {
myTv = tv;
}
public void execute() {
myTv.turnOff();
}
}
//频道切换命令ConcreteCommand
public class CommandChange implements Command {
private Tv myTv;
private int channel;
public CommandChange(Tv tv, int channel) {
myTv = tv;
this.channel = channel;
}
public void execute() {
myTv.changeChannel(channel);
}
}
//空命令
pubilc class NoCommand implements Command{
public void execute(){}
public void undo(){}
}
//Invoker
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();
}
}
//测试类Client
public class Client {
public static void main(String[] args) {
//在给命令赋值之前,我们可以将命令初始化为空命令
Command on = new Nocommand();
// 命令接收者Receiver
Tv myTv = new Tv();
// 开机命令ConcreteCommond
on = new CommandOn(myTv);
// 关机命令ConcreteCommond
CommandOff off = new CommandOff(myTv);
// 频道切换命令ConcreteCommond
CommandChange channel = new CommandChange(myTv, 2);
// 命令控制对象Invoker
Control control = new Control(on, off, channel);
// 开机
control.turnOn();
// 切换频道
control.changeChannel();
// 关机
control.turnOff();
}
}
其他
1、我们可以根据命令模式原理实现宏命令,所谓宏命令,就是在某个命令实现类的execute方法中调用多个receiver的具体执行方法,达到一次调用,执行多个方法的效果。
2、通过命令模式实现请求队列,命令可以将运算块打包(一个接受者和一组动作),然后将它传来传去,即使命令模式创建好久后,依然可以执行运算,甚至可以在不同线程中执行。想象有一个工作队列:你在某一端添加命令,另一端是一个线程,线程执行如下动作:从队列中取出命令,调用execute执行命令,将该命令丢弃,取出下一个命令。。。
3、通过命令模式进行日志记录,我们在执行命令时,将执行历史记录保存在磁盘上,这样一旦系统死机,我们可以根据记录的日志依次调用execute方法恢复系统
优点:
1.降低对象之间的耦合度。
2.新的命令可以很容易地加入到系统中。
3.可以比较容易地设计一个组合命令。
4.调用同一方法实现不同的功能
缺点:
使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。