设计模式之命令模式详解

一、命令模式的角色

    命令模式中角色共有四类,其中
 Client:模拟发出命令场景的测试类
 Invoker:命令的调用者,同时也是命令的接收者
 Command:命令的封装类,其中有指定命令具体的实现者
 Receiver:命令的执行者

    命令模式将命令封装成一个对象,在命令发出者、调用者之间传递,在创建命令的同时已经指定了具体的执行者。所以命令发出者无需知道命令的具体执行过程,只需要创建命令、并指定命令的执行者,然后再将命令交给调用者Invoker即可完成命令的流转。

    举个例子,如果你到饭馆点菜,服务员过来拿菜单给你点菜(你需要在菜单上勾画点的菜品),然后服务员将菜单交给后台对应的大厨做菜。在此过程中,菜单就是封装的Command类,不过其中已经指定好了哪个菜由哪个大厨(Receiver)来做;而服务员就是Invoker,我们只需要点好菜(创建命令),将菜单交给服务员即可。而服务员是怎么分配菜单给大厨以及大厨如何做菜都不需要关心,只要等着热腾腾、香喷喷的菜上桌即可。此处Client所在对Receiver的依赖,之后再加以讨论。

二、命令模式源码分析

package command;

public class Receiver {

	public void doSomethingA() {
		System.out.println("doA");
	}

	public void doSomethingB() {
		System.out.println("doB");
	}

}

package command;

public interface ICommand {

	void execute();

}
package command;

public class CommandA implements ICommand {

	private Receiver receiver;

	public CommandA(Receiver receiver) {
		super();
		this.receiver = receiver;
	}

	@Override
	public void execute() {
		receiver.doSomethingA();
	}

}
package command;

public class CommandB implements ICommand {

	private Receiver receiver;

	public CommandB(Receiver receiver) {
		super();
		this.receiver = receiver;
	}

	@Override
	public void execute() {
		receiver.doSomethingB();
	}

}
package command;

public class Invoker {
	private ICommand command;

	public void setCommand(ICommand command) {
		this.command = command;
	}
	
	public void action(){
		command.execute();
	}
}
package command;

public class Client {
	public static void main(String[] args) {
		Invoker invoker = new Invoker();
		Receiver receiver = new Receiver();
		ICommand command = new CommandA(receiver);
		invoker.setCommand(command);
		invoker.action();
	}
}
    仔细观察代码,大家也许会有些疑惑:Invoker直接调用ICommand接口的方法execute方法,ICommand的实现类再调用Receiver的具体命令实现方法,为何不直接在Client场景类中直接调用receiver的方法呢,这样是不是有点多此一举的味道?对于简单的问题确实如此,对于复杂的问题,比如命令队列、命令撤销、以及命令的扩展等就难以解决了。如同web应用需要分层一般,命令模式同样是将Invoker、Command、Receiver分离并面向接口变成来降低耦合性。Receiver类比于Dao层,做一些基本的实现功能;Command类比于Service层,处理一些具体命令的分解和整合的逻辑,可以组合多个Receiver类;Invoker则是处理Command集合。由于是面向接口编程,Command的扩展也变得容易,只需要添加实现类即可。

三、具体运用实例

    这里我们举个存款/取款的例子,并扩展一下上面的基本代码,加入命令撤销以及命令队列的功能。具体代码实现如下:
package account;

public class Account {

	private double amount;

	public Account(double amount) {
		super();
		this.amount = amount;
	}

	public double getAmount() {
		return amount;
	}

	public void saveMoney(double amount) {
		this.amount += amount;
	}

	public void drawMoney(double amount) {
		this.amount -= amount;
	}
	
}
package account;

public interface ICommand {

	void execute();

	void cancel();
}
package account;

public class SaveCommand implements ICommand {

	private Account account;
	private double amount;

	public SaveCommand(Account account, double amount) {
		this.account = account;
		this.amount = amount;
	}

	@Override
	public void execute() {
		account.saveMoney(amount);
	}

	@Override
	public void cancel() {
		account.drawMoney(amount);
	}

}
package account;

public class DrawCommand implements ICommand {

	private Account account;
	private double amount;

	public DrawCommand(Account account, double amount) {
		this.account = account;
		this.amount = amount;
	}

	@Override
	public void execute() {
		account.drawMoney(amount);
	}

	@Override
	public void cancel() {
		account.saveMoney(amount);
	}

}
package account;

import java.util.LinkedList;
import java.util.Queue;

public class Invoker {
	private Queue<ICommand> queue = new LinkedList<>();
	private ICommand command;

	public Invoker(Queue<ICommand> queue) {
		super();
		this.queue = queue;
	}

	public Invoker() {
		super();
	}

	public void addCommand(ICommand command) {
		queue.add(command);
	}

	public void action() {
		command = queue.poll();
		command.execute();
	}

	public void cancel() {
		if (command != null) {
			command.cancel();
		}
	}
}
package account;

public class Client {

	public static void main(String[] args) {
		Invoker invoker = new Invoker();
		Account account = new Account(1000);
		ICommand command = new SaveCommand(account, 1000);
		invoker.addCommand(command);
		invoker.action();
		System.out.println(account.getAmount());
		invoker.cancel();
		System.out.println(account.getAmount());
	}

}
    正如上一节所说,Account是具体存取钱的实现者,Invoker类处理Command集合或者是在Command之上的一些逻辑操作。在这里,我们添加了命令队列来控制命令的执行顺序,并将上次执行的命令保存下来,用来撤销上次执行的命令。命令撤销是命令模式的重要运用之一,利用物理上的联系(执行和撤销操作都放置在该命令实现类下)减少逻辑上的繁复性(撤销只需要考虑做执行的相反操作即可)。此处撤销操作较为简单,存款对应的相反操作就是取款,而实际运用中则可能遇到更加复杂的情况,可能涉及日志回滚等情况就不加以讨论了。

四、对基本命令模式的改进

    1.封闭Receiver类

    或许有部分细心的读者发现了,在我们举的第一个点菜的例子中,菜由哪位大厨来做并不需要由我们客人来指定,是已经指定好的(默认)。但是我们观察Command实现类或者是Client场景类就知道,在创建命令的时候是需要指定其具体的实现者Receiver的。所以在某些情况下,我们需要封闭Receiver,也就是去掉Client对于Receiver的依赖。对此,拿上个例子来说,我们对Command实现类做出如下修改:
package account;

public class DrawCommand implements ICommand {

	private Account account;
	private double amount;

	public DrawCommand(Account account, double amount) {
		this.account = account;
		this.amount = amount;
	}

	public DrawCommand(double amount) {
		account = new Account(0);
		this.amount = amount;
	}

	@Override
	public void execute() {
		account.drawMoney(amount);
	}

	@Override
	public void cancel() {
		account.saveMoney(amount);
	}

}

    2.添加Command抽象类

    ICommand接口过于抽象,不同种类的命令对应的实现者是可以预见的,比如我们是一个项目组,客户提新需求给项目经理,那么工作谁去做呢?当然是需求、开发、测试、美工组啦。所以可以这些可以预见实现者(Receivers)的命令抽象出来,拿前面取款的例子来说,可以针对账号相关的命令做抽象类:

package account;

public abstract class AccountCommand implements ICommand {
	protected Account account;

	public AccountCommand(Account account) {
		this.account = account;
	}

}
package account;

public class SaveCommand extends AccountCommand{

	private double amount;
	
	public SaveCommand(Account account,double amount) {
		super(account);
		this.amount = amount;
	}
	
	public SaveCommand(double amount) {
		super(new Account(0));
		this.amount = amount;
	}

	@Override
	public void execute() {
		account.saveMoney(amount);
	}

	@Override
	public void cancel() {
		account.drawMoney(amount);
	}

}

    这里的AccountCommand抽象类指定了Account作为实现者,申明为protected让子类继承,并实现了ICommand接口,必须重写执行、取消命令方法。

五、后序

    到这对于命令模式的解读就全部完成了,参考了<设计模式之禅>与<大话设计模式>两本书,之中包括了自己的一些理解,如有错误,欢迎指正。之后也会陆陆续续写写其余的模式,也希望大家多多支持,这是我坚持下去最大的动力,谢谢!



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值