SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

DeveloperZine(デベロッパージン)- エンジニアの意思決定を支える技術情報メディア ProductZine

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

デザインパターンを置き換えよう! Javaラムダ式によるシンプルコーディング

ラムダ式でTemplate Methodパターン+Factory Methodパターンで実装されたコードをシンプルにする ~ 継承から委譲、そして単なる手続きへ

デザインパターンを置き換えよう! Javaラムダ式によるシンプルコーディング 最終回

ジョブコントローラ(Abstract Factoryパターン)

 それでは、Factory Methodパターンを用いたジョブコントローラプログラムをAbstract Factoryパターンに変更してみましょう。

 これは相当に大きな変更ですから、やみくもにプログラム全体を書き換えてはいけません。次のように段階を踏んで、各ステップでテストを実行しながらリファクタリングを行うべきです[3]

  1. BackupJobDefinition クラスから BackupJobFactory クラスを抽出する(Extract Classリファクタリング)
    1. 空の BackupJobFactory クラスを作る
    2. BackupJobDefinition クラスに BackupJobFactory 型の jobMaker フィールドを追加し、コンストラクタで初期化する
    3. makeJob メソッド内の処理を BackupJobFactory クラスの get メソッドに移動し、makeJob メソッドは単に get メソッドを呼び出すようにする(Move Methodリファクタリング)
  2. BackupJobDefinition クラスの役割を JobDefinition クラスに統合する(Collapse Hierarchyリファクタリング)
    1. jobMaker フィールドを JobDefinition クラスに移動する。ただし型を Supplier<? extends J> 型とする(Pull Up Fieldリファクタリング)
    2. JobDefinition クラスのコンストラクタがファクトリを引数として取るようにする
    3. makeJob メソッドを JobDefinition クラスに移動する。ただし戻り値の型をJ型とする(Pull Up Methodリファクタリング)
    4. BackupJobDefinition クラスを使っている箇所を、JobDefinition クラスを使うように変更する
    5. 使われなくなった BackupJobDefinition クラスを削除する

[3] リファクタリングの名称は、Martin Fowler et al. 1999に準じます。

 最終的なリファクタリングの結果は、リスト3のようなプログラムになります。

リスト3: JobController3.java
import java.util.List;
import java.util.ArrayList;
import java.util.function.Supplier;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

/**
 * ジョブ定義 (Client).
 */
class JobDefinition<J extends Job> {

    /** ジョブのファクトリ. */
    private final Supplier<? extends J> jobMaker;

    /** オブザーバ一式. */
    private final List<JobObserver<? super J>> observers = new ArrayList<>();

    public JobDefinition(Supplier<? extends J> jobMaker) {
        this.jobMaker = jobMaker;
    }

    /** オブザーバを登録. */
    public void addObserver(JobObserver<? super J> observer) {
        this.observers.add(observer);
    }

    /** ジョブを作成、実行. */
    public final void execute() {
        J job = this.jobMaker.get();
        notifyObservers(job);
        job.execute();
        notifyObservers(job);
    }

    /** オブザーバにジョブの状態を通知. */
    private void notifyObservers(J job) {
        for (JobObserver<? super J> observer : this.observers) {
            observer.update(job);
        }
    }
}

/**
 * ジョブ (AbstractProduct).
 */
abstract class Job {

    /** ジョブの状態. */
    enum Status { UNFINISHED, FINISHED }

    /** ジョブの状態. */
    private Status status;

    Job() {
        this.status = Status.UNFINISHED;
    }

    /** ジョブの状態を戻す. */
    final Status getStatus() {
        return this.status;
    }

    /** ジョブが完了していたらtrueを戻す. */
    final boolean isFinished() {
        return this.status == Status.FINISHED;
    }

    /** ジョブを実行. */
    final void execute() {
        run();
        this.status = Status.FINISHED;
    }

    /** ジョブの処理の内容を実装. */
    abstract void run();
}

/**
 * ジョブ実行のオブザーバ.
 */
@FunctionalInterface
interface JobObserver<J extends Job> {
    void update(J job);
}

public class JobController3 {

    /**
     * データベースをバックアップするジョブのファクトリ (ConcreteFactory).
     */
    static class BackupJobFactory implements Supplier<BackupJob> {

        /** バックアップファイル名のフォーマッタ. */
        private static final DateTimeFormatter FILE_NAME_FORMATTER
            = DateTimeFormatter.ofPattern("'/backup/'yyyy_MM_dd'.sql'");

        @Override
        public BackupJob get() {
            LocalDate startDate = LocalDate.now();
            String fileName = startDate.format(FILE_NAME_FORMATTER);
            return new BackupJob(fileName);
        }
    }

    /**
     * データベースをバックアップするジョブ (ConcreteProduct).
     */
    static class BackupJob extends Job {

        /** バックアップファイルの名前. */
        private final String fileName;

        BackupJob(String fileName) {
            this.fileName = fileName;
        }

        /** バックアップファイルの名前を戻す. */
        String getFileName() {
            return this.fileName;
        }

        @Override
        void run() {
            System.out.printf("$ pg_dumpall >%s%n", this.fileName);
        }
    }

    public static void main(String[] args) {
        // バックアップジョブ定義
        JobDefinition<BackupJob> backupJobDefinition
            = new JobDefinition<>(new BackupJobFactory());

        // バックアップジョブの状態をログ出力するオブザーバを登録
        backupJobDefinition.addObserver(backupJob -> {
            System.out.printf("[INFO] バックアップ: %s ステータス: %s%n",
                    backupJob.getFileName(), backupJob.getStatus());
        });

        // バックアップジョブ完了をメール通知するオブザーバを登録
        String admin = "eva.lu.ator@example.org";
        backupJobDefinition.addObserver(backupJob -> {
            if (backupJob.isFinished()) {
                System.out.printf("$ echo '%sへのバックアップ完了!' | mail -s 'バックアップ通知' %s%n",
                        backupJob.getFileName(), admin);
            }
        });

        // バックアップジョブ実行
        backupJobDefinition.execute();
    }
}

 プログラムの実行結果は、Factory Methodパターンのプログラムと変わりません。

[INFO] バックアップ: /backup/2015_03_12.sql ステータス: UNFINISHED
$ pg_dumpall >/backup/2015_03_12.sql
[INFO] バックアップ: /backup/2015_03_12.sql ステータス: FINISHED
$ echo '/backup/2015_03_12.sqlへのバックアップ完了!' | mail -s 'バックアップ通知' eva.lu.ator@example.org

次のページ
ジョブコントローラ(ラムダ式)

この記事は参考になりましたか?

デザインパターンを置き換えよう! Javaラムダ式によるシンプルコーディング連載記事一覧

もっと読む

この記事の著者

宮川 拓(ミヤカワ タク)

日本Javaユーザーグループ幹事。東京のシステムインテグレータに勤務。Java VM上で動作する言語である「Kink」を開発中。相撲とアメリカ文学とスコティッシュポップを愛する。 ・ブログ: http://d.hatena.ne.jp/miyakawa_taku/ ・Twitter: @miyakawa_taku

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/8571 2015/03/27 14:00

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー