定义
将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。支持可撤销的操作。
特点
- 将发出请求的对象和执行请求的对象解耦。
- 通过command对象连接请求调用者与被调用者。
- 通过setCommand()方法改变调用者具体的执行体。
- 不同的command对象可以拥有不同的执行实体。
- 宏命令方式可以动态处理一系列的请求。
- 支持undo撤销。
实际中的应用
- 事务:借助堆栈来记录操作过程,然后逐一回滚到checkpoint
- 日志系统:如数据库的binlog,如果每一个操作都要备份整个数据库这工作量太大了。不妨记录日志,还原时可以从上个备份点开始逐一执行日志记录的动作。
- 线程池:将命令放入队列,线程池中的固定数量线程去队列里消费。
举例
场景
一个码农拥有一个音乐播放器和一台MacbookPro。它想一键执行或撤销如下等指令:
- 在MacbookPro上打开网易云音乐客户端
- 在MacbookPro上打开Chrome客户端
- 调高音乐播放器的声音
- 调低音乐播放器的声音
- ……
实现代码:
(1) MacbookPro
public class MacbookPro {
	public void openChrome(){
		System.out.println("open chrome.");
	}
	public void closeChrome(){
		System.out.println("close chrome");
	}
	public void openNeteaseMusic(){
		System.out.println("open netease music.");
	}
	public void closeNeteaseMusic(){
		System.out.println("close netease music.");
	}
}
(2) MusicPlayer
public class MusicPlayer {
	public static final String HIGH = "high";
	public static final String MEDIUM = "medium";
	public static final String LOW = "low";
	private String voice;
	
	public MusicPlayer(){
		voice = MEDIUM;
	}
	public String getVoice() {
		return voice;
	}
	public void setVoice(String voice) {
		this.voice = voice;
	}
	
	public void up(){
		String currentVoice = getVoice();
		switch (currentVoice) {
		case LOW:
			setVoice(MEDIUM);
			break;
		case MEDIUM:
		case HIGH:
		default:
			setVoice(HIGH);
		}
		System.out.println("Voice changed from " + currentVoice + " to " + getVoice());
	}
	
	public void down(){
		String currentVoice = getVoice();
		switch (currentVoice) {
		case HIGH:
			setVoice(MEDIUM);
			break;
		case MEDIUM:
		case LOW:
		default:
			setVoice(LOW);
		}
		
		System.out.println("Voice changed from " + currentVoice + " to " + getVoice());
	}
}
(3) Command接口
public interface Command {
	public void execute();
	public void undo();
}
(4) OpenChromeCommand
public class OpenChromeCommand implements Command{
	MacbookPro macbookPro;
	public OpenChromeCommand(MacbookPro macbookPro) {
		this.macbookPro = macbookPro;
	}
	public void execute() {
		macbookPro.openChrome();
	}
	public void undo() {
		macbookPro.closeChrome();
	}
}
(5) OpenNeteaseMusicCommand
public class OpenNeteaseMusicCommand implements Command{
	MacbookPro macbookPro;
	public OpenNeteaseMusicCommand(MacbookPro macbookPro) {
		this.macbookPro = macbookPro;
	}
	public void execute() {
		macbookPro.openNeteaseMusic();
	}
	public void undo() {
		macbookPro.closeNeteaseMusic();
	}
}
(6) UpVoiceCommand
public class UpVoiceCommand implements Command {
	MusicPlayer musicPlayer;
	String preVoice;
	public UpVoiceCommand(MusicPlayer musicPlayer) {
		this.musicPlayer = musicPlayer;
		preVoice = musicPlayer.getVoice();
	}
	
	public void execute() {
		preVoice = musicPlayer.getVoice();
		musicPlayer.up();
	}
	public void undo() {
		musicPlayer.setVoice(preVoice);
		System.out.println("Voice changed back to " + musicPlayer.getVoice());
	}
}
(7) DownVoiceCommand
public class DownVoiceCommand implements Command {
	MusicPlayer musicPlayer;
	String preVoice;
	
	public DownVoiceCommand(MusicPlayer musicPlayer) {
		this.musicPlayer = musicPlayer;
		preVoice = musicPlayer.getVoice();
	}
	
	public void execute() {
		preVoice = musicPlayer.getVoice();
		musicPlayer.down();
	}
	public void undo() {
		musicPlayer.setVoice(preVoice);
		System.out.println("Voice changed back to " + musicPlayer.getVoice());
	}
}
(8) Coder
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class Coder  {
	List<Command> commands;
	String coderName;
	public Coder(String coderName){
		commands = new ArrayList<>();
		this.coderName = coderName;
	}
	
	public String getCoderName() {
		return coderName;
	}
	public void setCoderName(String coderName) {
		this.coderName = coderName;
	}
	public void addCommand(Command command){
		if (!commands.contains(command)){
			this.commands.add(command);
		}
	}
	
	public void deleteCommamd(Command command){
		this.commands.remove(command);
	}
	
	public void setCommand(int index,  Command command){
		if (commands.size()>index){
			commands.set(index, command);
		}
	}
	
	public void replaceCommand(Command preCommand, Command curCommand){
		int index=commands.indexOf(preCommand);
		if ( index>=0 ){
			setCommand(index, curCommand);
		}
	}
	
	public void clearCommands(){
		commands.clear();
	}
	
	public void execute(){
		Iterator<Command> iterator = commands.iterator();
		if (!iterator.hasNext()){
			System.out.println("No command has been added.");
			return;
		}
		
		while (iterator.hasNext()){
			Command command = iterator.next();
			command.execute();
		}
	}
	
	public void undo(){
		Iterator<Command> iterator = commands.iterator();
		while (iterator.hasNext()){
			Command command = iterator.next();
			command.undo();
		}
	}
	
	public void print(){
		StringBuffer stringBuffer = new StringBuffer();
		Iterator<Command> iterator = commands.iterator();
		while (iterator.hasNext()){
			Command command = iterator.next();
			stringBuffer.append(command.getClass().getSimpleName() + "-");
		}
		stringBuffer.deleteCharAt(stringBuffer.length()-1);
		System.out.println(stringBuffer.toString());
	}
}
(9) Main测试类
public class Main {
	public static void main(String[] args) {
		Coder coder = new Coder("Nisir");
		System.out.println(coder.getCoderName());
		MacbookPro macbookPro = new MacbookPro();
		MusicPlayer musicPlayer = new MusicPlayer();
		Command  OpenChromeCommand = new OpenChromeCommand(macbookPro);
		Command OpenNeteaseMusicCommand = new OpenNeteaseMusicCommand(macbookPro);
		Command upVoiceCommand = new UpVoiceCommand(musicPlayer);
		Command downVoiceCommand = new DownVoiceCommand(musicPlayer);
		
		System.out.println("=============Dividing Line1=============");
		coder.addCommand(OpenChromeCommand);
		coder.execute();
		coder.undo();
		System.out.println("=============Dividing Line2=============");
		
		coder.setCommand(0, OpenNeteaseMusicCommand);
		coder.execute();
		coder.undo();
		System.out.println("=============Dividing Line3=============");
		
		coder.addCommand(OpenChromeCommand);
		coder.execute();
		coder.undo();
		System.out.println("=============Dividing Line4=============");
		
		coder.clearCommands();
		coder.execute();
		coder.undo();
		System.out.println("=============Dividing Line5=============");
		
		coder.addCommand(upVoiceCommand);
		coder.execute();
		
		System.out.println("=============Dividing Line6=============");
		
		coder.replaceCommand(upVoiceCommand, downVoiceCommand);
		coder.execute();
		coder.undo();
	}
}
(10) 测试结果:
Nisir
=============Dividing Line1=============
open chrome.
close chrome
=============Dividing Line2=============
open netease music.
close netease music.
=============Dividing Line3=============
open netease music.
open chrome.
close netease music.
close chrome
=============Dividing Line4=============
No command has been added.
=============Dividing Line5=============
Voice changed from medium to high
=============Dividing Line6=============
Voice changed from high to medium
Voice changed back to high
小结
可以看到,coder程序员不需要知道它要调用的具体对象,它只要通过执行不同command对象,就可以去调用具体的执行实体。并且command对象还可以改变。此外,通过ArrayList还可以动态增减命令集合,以后如果有新的命令加入,可以动态扩展,一键执行!
Azkaban中的命令模式
(1) EventListener——Command
package azkaban.event;
public interface EventListener {
  public void handleEvent(Event event);
}
(2) EventHandler——Coder
package azkaban.event;
import java.util.ArrayList;
import java.util.HashSet;
public class EventHandler {
  private HashSet<EventListener> listeners = new HashSet<EventListener>();
  public EventHandler() {
  }
  public void addListener(EventListener listener) {
    listeners.add(listener);
  }
  public void fireEventListeners(Event event) {
    ArrayList<EventListener> listeners =
        new ArrayList<EventListener>(this.listeners);
    for (EventListener listener : listeners) {
      listener.handleEvent(event);
    }
  }
  public void removeListener(EventListener listener) {
    listeners.remove(listener);
  }
}
命令模式 vs 观察者模式
个人感觉(可能并没有深刻体会到两者的精髓,希望有经验的人可以指点一二),两者非常相似,观察者模式中其实就嵌套使用了命令模式。
在Subject主题调notifyObservers()方法通知各个观察者时,会遍历所有已注册的观察者对象Observer,调observer.update()方法来更新各个对象。
观察者模式里,主题Subject类的registerObserver(Observer o)和removeObserver(Observer o)方法对应了命令模式里的add()和remove()。
在观察者模式里,observer会有一个实例变量持有主题subject的引用,从而可以在observer类里调用subject.registerObserver()方法和subject.removeObserver()方法来向主题注册和注销自己。