導入
リモコンの「ボタンを押す」という操作を、後から「取り消す(undo)」ことができたら便利です。でも、押した操作の履歴を「文字列」や「番号」で覚えても、それをどう元に戻すかの処理は結局どこかに書く必要があります。Command(コマンド) は、「実行する操作」そのものを1つのオブジェクトとして扱う パターンです。操作をオブジェクト化すると、履歴に貯めておいたり、後から実行したり、取り消したりが自然にできます。
説明
「電気をつける」「電気を消す」という操作それぞれを、execute()(実行)と undo()(取り消し)を持つオブジェクトとして表現します。
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
Light light = new Light();
Command on = new LightOnCommand(light);
Command off = new LightOffCommand(light);
RemoteControl remote = new RemoteControl();
remote.press(on);
remote.press(off);
remote.press(on);
remote.undo();
}
}
interface Command {
void execute();
void undo();
}
class Light {
void on() {
System.out.println("電気: ON");
}
void off() {
System.out.println("電気: OFF");
}
}
class LightOnCommand implements Command {
private Light light;
LightOnCommand(Light light) {
this.light = light;
}
public void execute() {
light.on();
}
public void undo() {
light.off();
}
}
class LightOffCommand implements Command {
private Light light;
LightOffCommand(Light light) {
this.light = light;
}
public void execute() {
light.off();
}
public void undo() {
light.on();
}
}
class RemoteControl {
private List<Command> history = new ArrayList<>();
void press(Command command) {
command.execute();
history.add(command);
}
void undo() {
if (history.size() > 0) {
Command last = history.get(history.size() - 1);
history.remove(history.size() - 1);
last.undo();
}
}
}
classDiagram
Command <|.. LightOnCommand
Command <|.. LightOffCommand
RemoteControl --> "many" Command : history
LightOnCommand --> Light
LightOffCommand --> Light
class Command {
<<interface>>
+execute()
+undo()
}
Lightは「電気をつける/消す」という実際の処理を持つ相手(受け手) です。Light自身は「操作の履歴」や「取り消し」を知りません。LightOnCommandはLightを1つ持ち、execute()でlight.on()を呼び、undo()で逆のlight.off()を呼びます。「操作」と「取り消し方」がセットでオブジェクト化されています。RemoteControlはpress(Command)されるたびに、実行しつつhistoryに積んでおきます。undo()では最後に積んだコマンドを取り出し、そのundo()を呼ぶだけです。RemoteControlはどんな操作なのか中身を知らなくても取り消せます。- Strategyと形は似ていますが、Strategyは「アルゴリズムの選択」、Commandは「操作の実行・取り消し・履歴管理」に主眼があります。
やってみよう
下のエディタを実行し、ON → OFF → ON と押した後の undo() が、直前の操作(ON)を打ち消して OFF になることを確認しましょう。remote.undo() をもう一度呼ぶとどうなるか試してみてください。
演習
Tv クラス(on() で テレビ: ON、off() で テレビ: OFF と表示)と、それに対応する TvOnCommand(Command を実装し、execute() で tv.on()、undo() で tv.off())を作ってください。main で Tv tv = new Tv(); Command on = new TvOnCommand(tv); を作り、RemoteControl で press(on) した後に undo() を呼んでください。
ヒント1を見る
Light/LightOnCommand の作りをそのまま Tv/TvOnCommand に置き換えます。
ヒント2を見る
remote.press(on) で テレビ: ON、続く remote.undo() で テレビ: OFF が表示されます。