1.概念
将来自客户端的请求传入一个对象,从而使你可用不同的请求对客户进行参数化。用于“行为请求者”与“行为实现者”解耦,可实现二者之间的松耦合,以便适应变化。分离变化与不变的因素。
在面向对象的程序设计中,一个对象调用另一个对象,一般情况下的调用过程是:创建目标对象实例;设置调用参数;调用目标对象的方法。
但在有些情况下有必要使用一个专门的类对这种调用过程加以封装,我们把这种专门的类称作command类。
Command模式可应用于
a)整个调用过程比较繁杂,或者存在多处这种调用。这时,使用Command类对该调用加以封装,便于功能的再利用。
b)调用前后需要对调用参数进行某些处理。
c)调用前后需要进行某些额外处理,比如日志,缓存,记录历史操作等。
Command模式有如下效果:
a)将调用操作的对象和知道如何实现该操作的对象解耦。
b)Command是头等对象。他们可以像其他对象一样被操作和扩展。
c)你可将多个命令装配成一个符合命令。
d)增加新的Command很容易,因为这无需改变现有的类。
2 模式结构和说明
命令模式的结构如图2所示:
图2 命令模式结构图
Command:
定义命令的接口,声明执行的方法。
ConcreteCommand:
命令接口实现对象,是“虚”的实现;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
Receiver:
接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
Invoker:
要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
Client:
创建具体的命令对象,并且设置命令对象的接收者。注意这个不是我们常规意义上的客户端,而是在组装命令对象和接收者,或许,把这个Client称为装配者会更好理解,因为真正使用命令的客户端是从Invoker来触发执行。
3举例说明:
张三晚上到烧烤店吃夜宵,进店子坐下后,服务员过来接待,张三对服务员说来份烤羊肉和烤鸡翅,随后服务员去通知烧烤师傅,让其烧烤。
UML
package wei.com;
//烧烤师傅--Receiver
public class Barbecuer
{
public void kaoyangrou() //烤羊肉动作
{
System.out.println("烤羊肉");
}
public void kaojichi() //烤鸡翅动作
{
System.out.println("烤鸡翅");
}
}
package wei.com;
abstract public class Command
{
protected Barbecuer reciver;
public Command(Barbecuer barbecuer)
{
this.reciver = barbecuer;
}
//执行命令
abstract public void ExcuteCommand();
public String toString()
{
return "命令模式.";
}
}
package wei.com;
public class KaoyangrouCommand extends Command
{
public KaoyangrouCommand(Barbecuer barbecuer)
{
super(barbecuer);
}
@Override
public void ExcuteCommand()
{
this.reciver.kaoyangrou();
}
public String toString()
{
return super.toString()+"烤羊肉";
}
}
package wei.com;
public class KaojichiCommand extends Command
{
public KaojichiCommand(Barbecuer barbecuer)
{
super(barbecuer);
}
@Override
public void ExcuteCommand()
{
this.reciver.kaojichi();
}
public String toString()
{
return super.toString()+"烤鸡翅";
}
}
package wei.com;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
//---Invoker
public class Waiter
{
private ArrayList<Command> orders = new ArrayList<Command>();
//添加命令
public void addCommand(Command command)
{
if(command.toString().equals("命令模式.烤羊肉") )
{
System.out.println("对不起,烤羊肉卖完了,请点其他菜");
}
else
{
orders.add(command);
String msg="";
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("YYYY/MM/dd HH:mm:ss.SSS");
msg+= sdf.format(date);
System.out.println("增加订单:" +command.toString()+" Time: "+msg);
}
}
//删除命令
public void removeCommand(Command command)
{
orders.remove(command);
String msg="";
Date date = new Date();
SimpleDateFormat sdf = new SimpleDateFormat("YYYY/MM/dd HH:mm:ss.SSS");
msg+= sdf.format(date);
System.out.println("取消订单:" +command.toString()+" Time: "+msg);
}
//通知
public void Notify()
{
for (Command eCommand : orders)
{
eCommand.ExcuteCommand();
}
}
}
package wei.com;
public class Client
{
public static void main(String[] args) throws InterruptedException
{
//开店前奏
Barbecuer barbecuer = new Barbecuer(); //厨师准备好
Command button1 = new KaojichiCommand(barbecuer); //按键1--点烤鸡翅
Command button2 = new KaoyangrouCommand(barbecuer);//按键2--点烤羊肉
Waiter waiter = new Waiter(); //服务员准备好
//顾客 开始点菜--服务员服务
waiter.addCommand(button1); //点烤鸡翅
Thread.sleep(1000*1);
waiter.addCommand(button2); //点烤羊肉
Thread.sleep(1000*1);
waiter.removeCommand(button1);//取消烤鸡翅
waiter.Notify();
}
}
4.应用场景
在下面的情况下应当考虑使用命令模式:
1)使用命令模式作为"CallBack"在面向对象系统中的替代。"CallBack"讲的便是先将一个函数登记上,然后在以后调用此函数。
2)需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命期。换言之,原先的请求发出者可能已经不在了,而命令对象本身仍然是活动的。这时命令的接收者可以是在本地,也可以在网络的另外一个地址。命令对象可以在串形化之后传送到另外一台机器上去。
3)系统需要支持命令的撤消(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo()方法,把命令所产生的效果撤销掉。命令对象还可以提供redo()方法,以供客户端在需要时,再重新实施命令效果。
4)如果一个系统要将系统中所有的数据更新到日志里,以便在系统崩溃时,可以根据日志里读回所有的数据更新命令,重新调用Execute()方法一条一条执行这些命令,从而恢复系统在崩溃前所做的数据更新。