技術メモ

技術メモ

ラフなメモ

Javaのデザインパターンを整理(振る舞い編)

Javaデザインパターンについて復習します。プログラムをデザインするにあたって、参考にできる引き出しは多く持っておきたいです。

デザインパターンとは

過去のソフトウェア設計者が発見し編み出した設計ノウハウを蓄積し、名前をつけ、再利用しやすいように特定の規約に従ってカタログ化したもの

有名なのはGoFの23のデザインパターンです。それぞれのデザインパターンは3つの分類に分かれています。

  • オブジェクトの「生成」に関するパターン
  • プログラムの「構造」に関するパターン
  • オブジェクトの「振る舞い」に関するパターン

振る舞いに関するパターン

パターン名 概要
Chain of Responsibility イベントの送受信を行う複数のオブジェクトを鎖状につなぎ、それらの間をイベントが渡されてゆくようにする。
Command 複数の異なる操作について、それぞれに対応するオブジェクトを用意し、オブジェクトを切り替えることで、操作の切替えを実現する。
Interpreter 構文解析のために、文法規則を反映するクラス構造を作る。
Iterator 複数の要素を内包するオブジェクトのすべての要素に対して、順番にアクセスする方法を提供する。反復子。
Mediator オブジェクト間の相互作用を仲介するオブジェクトを定義し、オブジェクト間の結合度を低くする。
Memento データ構造に対する一連の操作のそれぞれを記録しておき、以前の状態の復帰または操作の再現が行えるようにする。
Observer (出版-購読型モデル) インスタンスの変化を他のインスタンスから監視できるようにする。Listenerとも呼ばれる。
State オブジェクトの状態を変化させることで、処理内容を変えられるようにする。
Strategy データ構造に対して適用する一連のアルゴリズムカプセル化し、アルゴリズムの切替えを容易にする。
Template Method あるアルゴリズムの途中経過で必要な処理を抽象メソッドに委ね、その実装を変えることで処理が変えられるようにする。
Visitor データ構造を保持するクラスと、それに対して処理を行うクラスを分離する。

Commandパターン

  • 概要
    • 「命令」をインスタンスとして扱うことにより、処理の組み合わせや切り替えなどを容易にするパターン。
  • 何が嬉しいか
    • 処理が多岐に渡る場合や組み合わせて処理を行う場合に、コードの見通しが良くなります。
クラス図

f:id:tutuz:20190512084401p:plain

実装例

SQLを実行するモードがいくつかあり、いろいろなモードで任意のSQLを実行できるようなコマンドを実行するパターン。Clientからは宣言的に実行したいモードで実行できるのが嬉しいです。if文で分岐し始めると大変です。

Command

public abstract class Command {

    protected String sql;

    public void setSql(String sql) {
        this.sql = sql;
    }

    public abstract void execute();
}

ConcreteCommand

public class GetParamCommand extends Command {
    @Override
    public void execute() {
        // DBへのConnectionなど
        // SQLの実行結果をパラメータとして取得するなど
    }
}

public class UpdateDataCommand extends Command {
    @Override
    public void execute() {
        // DBへのConnectionなど
        // Insertなどの実行
    }
}

Client

public class Main {

    public static void main(String[] args) {

        String getParamSql = "select * from ...";
        String updateDataSql = "insert into ...";

        Command updateDataCommand = new UpdateDataCommand();
        Command getParamCommand = new GetParamCommand();

        updateDataCommand.setSql(updateDataSql);
        updateDataCommand.execute();

        getParamCommand.setSql(getParamSql);
        getParamCommand.execute();
    }
}

Strategyパターン

  • 概要
    • 処理アルゴリズムを容易に切り替えられるようにするパターン
    • Commandパターンと似ている
  • 何が嬉しいか
    • 処理が多岐に渡る場合や組み合わせて処理を行う場合に、コードの見通しが良くなります。
クラス図

f:id:tutuz:20190512124959p:plain

実装例

Strategy

public interface Strategy {
    public void process(String sql);
}

ConcreteStrategy

public class GetParamStrategy implements Strategy {
    @Override
    public void process(String sql) {
        // SQLの実行結果をパラメータとして取得するなど
    }
}

ConcreteStrategy

public class UpdateDataStrategy implements Strategy {
    @Override
    public void process(String sql) {
        // Insertなどの実行など
    }
}

ConcreteStrategy

public class DefaultStrategy implements Strategy {
    @Override
    public void process(String sql) {
        // デフォルトの処理
    }
}

Context

public class Context {
    private Strategy strategy;

    public Context() {
        this.strategy = new DefaultStrategy();
    }

    public void setStrategy(Strategy strategy) {
        this.strategy = strategy;
    }

    public void execute(String sql) {
        this.strategy.process(sql);
    }
}

Client

public class Main {
    public static void main(String[] args) {
        String getParamSql = "select * from ...";
        String updateDataSql = "insert into ...";

        Strategy getParamStrategy = new GetParamStrategy();
        Strategy updateDataStrategy = new UpdateDataStrategy();

        Context context = new Context();
        context.setStrategy(getParamStrategy);
        context.execute(getParamSql);

        context.setStrategy(updateDataStrategy);
        context.execute(updateDataSql);
    }
}
ライブラリでの利用
  • java.util.Comparator#compare()
  • javax.servlet.http.HttpServlet

Observerパターン

  • 概要
    • プログラム内のオブジェクトのイベントを他のオブジェクトへ通知する処理で使われるデザインパターン
    • 状態が変化する側が通知する仕組みを持ちます
  • 何が嬉しいか
    • 状態を通知する際に、通知側と受信側のクラスを疎結合にすることができます
    • ObserverインターフェースとSubject抽象クラスがゆるく結合しているために、具象クラスの実装を知る必要がありません
クラス図

f:id:tutuz:20190512165827p:plain

実装例

Observer

public interface Observer {
    void update(Subject subject);
}

Subject

public abstract class Subject {
    private List<Observer> observers = new ArrayList<>();

    public void addObserver(Observer observer) {
        observers.add(observer);
    }

    public void notifyObservers() {
        for (Observer observer : observers) {
            observer.update(this);
        }
    }
    public abstract void execute();
}

ConcereteObserver

public class Client implements Observer {
    @Override
    public void update(Subject subject) {
        System.out.println("通知を受信しました");
    }
}

public class ClientEn implements Observer {

    @Override
    public void update(Subject subject) {
        System.out.println("Accepted notification.");
    }
}

ConcreteSubject

public class DataChanger extends Subject {
    private int status;

    @Override
    public void execute() {
        status++;
        System.out.printf("ステータスが%sに変わりました。\n", String.valueOf(status));
        notifyObservers();
    }
}

テスト

public class Main {

    public static void main(String[] args) {
        Observer observer = new Client();
        Observer observer2 = new ClientEn();
        Subject dataChanger = new DataChanger();

        dataChanger.addObserver(observer);
        dataChanger.addObserver(observer2);
        for (int count = 0; count < 10; count++) {
            dataChanger.execute();

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

結果

ステータスが1に変わりました。
通知を受信しました
Accepted notification.
ステータスが2に変わりました。
通知を受信しました
Accepted notification.
ステータスが3に変わりました。
通知を受信しました
Accepted notification.
...
ライブラリでの利用
  • java.util.Observer
  • java.util.Observable
  • java.util.EventListener

参考

Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで

Java本格入門 ~モダンスタイルによる基礎からオブジェクト指向・実用ライブラリまで