SHOEISHA iD

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

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

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

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

ラムダ式でObserverパターンで実装されたコードをシンプルにする ~ オブザーバオブジェクトはラムダ式によって簡潔に生成できる

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


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

 それではここから、簡易的なジョブコントローラを題材として、Observerパターンのプログラムが、ラムダ式を使ってどのように書き直せるかを見ていきます。

 このジョブコントローラでは、主に次の役割が登場します。

  • ジョブ定義:ジョブを生成、実行する。ジョブの状態が変わった時に、登録されたジョブオブザーバに通知する
  • ジョブ:バッチ処理を行う。未実行/実行済みの状態を持つ
  • ジョブオブザーバ:メール送付やログ出力など、ジョブの状態に対応する処理を行う

 まずは、Observerパターンを使って実装した場合のプログラムを見てみましょう。プログラムのクラス図は図4のとおりです。

図4:ジョブコントローラプログラムのクラス図(Observerパターン)
図4:ジョブコントローラプログラムのクラス図(Observerパターン)

 

 それぞれのクラス、インタフェースの役割は次のとおりです。

  • JobDefinition:ジョブ定義の抽象クラス。Subjectの役割の一部を担い、ジョブオブザーバの登録・通知を担う
  • BackupJobDefinition:バックアップジョブのジョブ定義クラス。ConcreteSubjectの役割の一部を担う
  • Job:ジョブの抽象クラス。Subjectの役割の一部を担い、状態の保持と変更を担う
  • BackupJob:バックアップジョブのクラス。ConcreteSubjectの役割の一部を担う
  • JobObserver:ジョブオブザーバのインタフェース。Observerの役割を担う
  • LogBackupJobObserver:ジョブの状態をログ出力するジョブオブザーバ。ConcreteObserverの役割を担う
  • MailBackupJobObserver:ジョブ完了のメールを送付するジョブオブザーバ。ConcreteObserverの役割を担う

 また、Observerパターンを用いたプログラムのシーケンス図は図5のとおりです(主要なクラス・メソッドのみ記載しています)。

図5 ジョブコントローラプログラムのシーケンス図(Observerパターン)
図5 ジョブコントローラプログラムのシーケンス図(Observerパターン)

 

 JobController1 クラスは、BackupJobDefinition クラスのジョブ定義にジョブオブザーバを登録します。BackupJobDefinition クラスの execute メソッドが呼ばれると、BackupJob クラスのジョブを作成し、その execute メソッドを呼び出します。ジョブの execute メソッドを呼ぶ前後に、ジョブオブザーバに状態変更が通知されます。

 Observerパターンを用いたプログラムをリスト2に掲げます。

リスト2:JobController1.java ~ Observerパターンを用いたジョブコントローラプログラム
import java.util.List;
import java.util.ArrayList;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;

/**
 * ジョブ定義 (Subjectその1).
 */
abstract class JobDefinition<J extends Job> {

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

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

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

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

    /** ジョブを作成するファクトリメソッド. */
    abstract protected J makeJob();
}

/**
 * ジョブ (Subjectその2).
 */
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();
}

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

public class JobController1 {

    /**
     * データベースをバックアップするジョブの定義 (ConcreteSubjectその1).
     */
    static class BackupJobDefinition extends JobDefinition<BackupJob> {

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

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

    /**
     * データベースをバックアップするジョブ (ConcreteSubjectその2).
     */
    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);
        }
    }

    /**
     * バックアップジョブ完了をログ出力するオブザーバ.
     */
    static class LogBackupJobObserver implements JobObserver<BackupJob> {
        @Override
        public void update(BackupJob backupJob) {
            System.out.printf("[INFO] バックアップ: %s ステータス: %s%n",
                    backupJob.getFileName(), backupJob.getStatus());
        }
    }

    /**
     * バックアップジョブ完了をメール通知するオブザーバ.
     */
    static class MailBackupJobObserver implements JobObserver<BackupJob> {

        /** 通知先メールアドレス. */
        private final String mailAddress;

        MailBackupJobObserver(String mailAddress) {
            this.mailAddress = mailAddress;
        }

        @Override
        public void update(BackupJob backupJob) {
            if (backupJob.isFinished()) {
                System.out.printf("$ echo '%sへのバックアップ完了!' | mail -s 'バックアップ通知' %s%n",
                        backupJob.getFileName(), this.mailAddress);
            }
        }
    }

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

        // バックアップジョブの状態をログ出力するオブザーバを登録
        backupJobDefinition.addObserver(new LogBackupJobObserver());

        // バックアップジョブ完了をメール通知するオブザーバを登録
        String admin = "eva.lu.ator@example.org";
        backupJobDefinition.addObserver(new MailBackupJobObserver(admin));

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

 実行結果は次のとおりです。

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

 BackupJobDefinition クラスや BackupJob クラスの中では、ジョブ状態変更を受けてのログ出力やメール送信の処理を記述することなく、オブザーバオブジェクトに託しています。

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

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

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

もっと読む

この記事の著者

宮川 拓(ミヤカワ タク)

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

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

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

この記事をシェア

CodeZine(コードジン)
https://codezine.jp/article/detail/8504 2015/03/12 21:10

イベント

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

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

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

メールバックナンバー