设计模式学习(五)——命令模式

定义

将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。支持可撤销的操作。

特点

  1. 将发出请求的对象和执行请求的对象解耦。
  2. 通过command对象连接请求调用者与被调用者。
  3. 通过setCommand()方法改变调用者具体的执行体。
  4. 不同的command对象可以拥有不同的执行实体。
  5. 宏命令方式可以动态处理一系列的请求。
  6. 支持undo撤销。

实际中的应用

  1. 事务:借助堆栈来记录操作过程,然后逐一回滚到checkpoint
  2. 日志系统:如数据库的binlog,如果每一个操作都要备份整个数据库这工作量太大了。不妨记录日志,还原时可以从上个备份点开始逐一执行日志记录的动作。
  3. 线程池:将命令放入队列,线程池中的固定数量线程去队列里消费。

举例

场景

一个码农拥有一个音乐播放器和一台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()方法来向主题注册和注销自己。

原文地址:https://www.cnblogs.com/znicy/p/6753253.html