ジョブコントローラ(Abstract Factoryパターン)
それでは、Factory Methodパターンを用いたジョブコントローラプログラムをAbstract Factoryパターンに変更してみましょう。
これは相当に大きな変更ですから、やみくもにプログラム全体を書き換えてはいけません。次のように段階を踏んで、各ステップでテストを実行しながらリファクタリングを行うべきです[3]。
-
BackupJobDefinitionクラスからBackupJobFactoryクラスを抽出する(Extract Classリファクタリング)-
空の
BackupJobFactoryクラスを作る -
BackupJobDefinitionクラスにBackupJobFactory型のjobMakerフィールドを追加し、コンストラクタで初期化する -
makeJobメソッド内の処理をBackupJobFactoryクラスのgetメソッドに移動し、makeJobメソッドは単にgetメソッドを呼び出すようにする(Move Methodリファクタリング)
-
空の
-
BackupJobDefinitionクラスの役割をJobDefinitionクラスに統合する(Collapse Hierarchyリファクタリング)-
jobMakerフィールドをJobDefinitionクラスに移動する。ただし型をSupplier<? extends J>型とする(Pull Up Fieldリファクタリング) -
JobDefinitionクラスのコンストラクタがファクトリを引数として取るようにする -
makeJobメソッドをJobDefinitionクラスに移動する。ただし戻り値の型をJ型とする(Pull Up Methodリファクタリング) -
BackupJobDefinitionクラスを使っている箇所を、JobDefinitionクラスを使うように変更する -
使われなくなった
BackupJobDefinitionクラスを削除する
-
[3] リファクタリングの名称は、Martin Fowler et al. 1999に準じます。
最終的なリファクタリングの結果は、リスト3のようなプログラムになります。
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
