本文へスキップ
BecomeCoder

Javaデザインパターンコース · 第4章 振る舞いに関するパターン ― オブジェクト同士の「連携の仕方」を工夫する · レッスン12

Command ― 「操作」そのものをオブジェクトにする

ブラウザで完結

導入

リモコンの「ボタンを押す」という操作を、後から「取り消す(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 自身は「操作の履歴」や「取り消し」を知りません。
  • LightOnCommandLight を1つ持ち、execute()light.on() を呼び、undo() で逆の light.off() を呼びます。「操作」と「取り消し方」がセットでオブジェクト化されています。
  • RemoteControlpress(Command) されるたびに、実行しつつ history積んでおきます。undo() では最後に積んだコマンドを取り出し、その undo() を呼ぶだけです。RemoteControl はどんな操作なのか中身を知らなくても取り消せます
  • Strategyと形は似ていますが、Strategyは「アルゴリズムの選択」、Commandは「操作の実行・取り消し・履歴管理」に主眼があります。

やってみよう

下のエディタを実行し、ON → OFF → ON と押した後の undo() が、直前の操作(ON)を打ち消して OFF になることを確認しましょう。remote.undo() をもう一度呼ぶとどうなるか試してみてください。

演習

Tv クラスon()テレビ: ONoff()テレビ: OFF と表示)と、それに対応する TvOnCommandCommand を実装し、execute()tv.on()undo()tv.off())を作ってください。mainTv tv = new Tv(); Command on = new TvOnCommand(tv); を作り、RemoteControlpress(on) した後に undo() を呼んでください。

ヒント1を見る

Light/LightOnCommand の作りをそのまま Tv/TvOnCommand に置き換えます。

ヒント2を見る

remote.press(on)テレビ: ON、続く remote.undo()テレビ: OFF が表示されます。

実際に動かしてみよう

下のエディタにJavaを書いて「実行」を押すと、学習用シミュレータが System.out.println の出力を表示します(本物のJVMではなく、教材の範囲を再現した軽量エンジンです)。クラス・継承・コレクションなども動きます。Scanner で入力を読む例は「標準入力」欄に値を入れてください。本文の例を書き換えて動かしてみましょう(Stream・ラムダ・スレッド・ファイル入出力など一部の機能は対象外です)。

Java — ブラウザ内で実行(学習用シミュレータ)

Javaの教材サブセットを動かす学習用シミュレータを読み込みます(本物のJVMではなく、動きを再現した軽量な自作エンジンです)。
スクロールして表示された時点でも自動で読み込まれます。