2015年6月15日月曜日

1-14. Command パターン

リクエストをオブジェクトにカプセル化することで、リクエストの情報はパラメータされ、
また取り消し操作をサポートします。



client はリクエストに応じた ConcreteCommand オブジェクトを作成します。
リクエストは client -> Invoker -> ConcreteCommand -> Receiver の順に処理されます。
また、Invoker では Command の保存処理が行われます。
取り消し操作は Invoker に保存された Command を取り出し、同様に処理されます。
void client() {
	Invoker invoker = new Invoker();
	invoker.execute(new ConcreteCommandA());
	invoker.execute(new ConcreteCommandB());
	invoker.undo();
}
interface Command {
	public void execute(Receiver receiver);
	public void undo(Receiver receiver);
}
class ConcreteCommandA implements Command {
	public void execute(Receiver receiver) { receiver.actionA(); }
	public void undo(Receiver receiver) { receiver.undoA(); }
}
class ConcreteCommandB implements Command {
	public void execute(Receiver receiver) { receiver.actionB(); }
	public void undo(Receiver receiver) { receiver.undoB(); }
}
class Receiver {
	public void actionA() { System.out.println("actionA"); }
	public void actionB() { System.out.println("actionB"); }
	public void undoA() { System.out.println("undoA"); }
	public void undoB() { System.out.println("undoB"); }
}
class Invoker {
	private LinkedList<Command> commands = new LinkedList<>();
	private Receiver receiver = new Receiver();
	public void execute(Command command) {
		this.commands.push(command);
		command.execute(this.receiver);
	}
	public void undo() {
		this.commands.pop().undo(this.receiver);
	}
}
実行結果:
actionA
actionB
undoB

サンプルプログラムとして、簡易的なエディタを示します。
テキスト入力、カーソル移動といった操作を Command 化しています。
実行結果:
// 直接実行
EditorReceiver.input(): hello 
EditorReceiver.input(): japan
EditorReceiver.backspace(): 5
EditorReceiver.input(): word
EditorReceiver.cursor(): -1
EditorReceiver.input(): l
hello world
// Command パターン
EditorReceiver.input(): hello 
EditorReceiver.input(): japan
EditorInvoker.undo(): input[japan]
EditorReceiver.backspace(): 5
EditorInvoker.execute(): remove undo -> input[japan]
EditorReceiver.input(): word
EditorReceiver.cursor(): -1
EditorReceiver.input(): l
hello world
package design_pattern;

import java.util.LinkedList;
import java.util.ListIterator;

/**
 * Command
 * GoF
 * リクエストをオブジェクトにカプセル化することで、リクエストの情報はパラメータされ、
 * また取り消し操作をサポートします。
 * 
 * @author 2015/05/28 matsushima
 */
public class CommandSample {
	/**
	 * main
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		new CommandSample().client();
	}
	/**
	 * Client
	 */
	void client() {
		System.out.println("// 直接実行");
		EditorReceiver receiver = new EditorReceiver();
		receiver.input("hello ");
		receiver.input("japan");
		receiver.backspace(5);
		receiver.input("word");
		receiver.cursor(-1);
		receiver.input("l");
		System.out.println(receiver.result());
		System.out.println("// Command パターン");
		EditorInvoker invoker = new EditorInvoker();
		invoker.execute(new InputConcreteCommand("hello "));
		invoker.execute(new InputConcreteCommand("japan"));
		invoker.undo();
		invoker.execute(new InputConcreteCommand("word"));
		invoker.execute(new CursorConcreteCommand(-1));
		invoker.execute(new InputConcreteCommand("l"));
		System.out.println(invoker.result());
	}

	/** エディタのコマンドのインターフェース。 */
	public interface EditorCommand {
		public void execute(EditorReceiver receiver);
		public void undo(EditorReceiver receiver);
	}
	/** テキスト入力コマンド。 */
	public class InputConcreteCommand implements EditorCommand {
		private String text;
		public InputConcreteCommand(String text) {
			this.text = text;
		}
		public void execute(EditorReceiver receiver) {
			receiver.input(this.text);
		}
		public void undo(EditorReceiver receiver) {
			receiver.backspace(this.text.length());
		}
		public String toString() {
			return "input[" + this.text + "]";
		}
	}
	/** カーソル入力コマンド。 */
	public class CursorConcreteCommand implements EditorCommand {
		private int distance;
		public CursorConcreteCommand(int distance) {
			this.distance = distance;
		}
		public void execute(EditorReceiver receiver) {
			receiver.cursor(this.distance);
		}
		public void undo(EditorReceiver receiver) {
			receiver.cursor(-this.distance);
		}
		public String toString() {
			return "cursor[" + this.distance + "]";
		}
	}
	/** エディタレシーバ。 */
	public class EditorReceiver {
		private StringBuilder text = new StringBuilder();
		private int cursorX = 0;
		public void input(String text) {
			System.out.println("EditorReceiver.input(): " + text);
			this.text.insert(this.cursorX, text);
			this.cursorX += text.length();
		}
		public void backspace(int distance) {
			System.out.println("EditorReceiver.backspace(): " + distance);
			this.text.delete(this.cursorX - distance, this.cursorX);
			this.cursorX -= distance;
		}
		public void cursor(int distance) {
			System.out.println("EditorReceiver.cursor(): " + distance);
			this.cursorX += distance;
		}
		public String result() {
			return this.text.toString();
		}
	}
	/** エディタインボーカ。 */
	public class EditorInvoker {
		private LinkedList<EditorCommand> commands = new LinkedList<>();
		private ListIterator<EditorCommand> commandIt = commands.listIterator();
		EditorReceiver receiver = new EditorReceiver();
		public void execute(EditorCommand command) {
			// undo 済みを削除
			while (this.commandIt.hasNext()) {
				EditorCommand c = this.commandIt.next();
				System.out.println("EditorInvoker.execute(): remove undo -> " + c);
				this.commandIt.remove();
			}
			// command 登録
			this.commandIt.add(command);
			// command 実行
			command.execute(receiver);
		}
		public void undo() {
			// command undo
			EditorCommand command = this.commandIt.previous();
			System.out.println("EditorInvoker.undo(): " + command);
			command.undo(this.receiver);
		}
		public String result() {
			return this.receiver.result();
		}
	}
}

0 件のコメント:

コメントを投稿