デザインパターン[Decorator]

さて、久々(?)のデザインパターンです。
今回はDecoratorパターン。
装飾という意味です。

まずは実例から。


public final class ConfigUtil {
public static int getParameter() {
// どこかから設定を取ってくる
}
}

という設定を管理するクラスがあります。
こgetParameter()実は処理が重く、
1つのアプリケーションから複数回呼び出されることがあり、
そこがボトルネックとなっている場合、
何らかの処置が必要です。

考えられる処置をあげてみましょう。
?アプリ側で値を変数にとっておき、それを使うように修正する。
これは一番に思い浮かびますね。

int param = ConfigUtil.getParameter();
//これ以降はparamを使い、ConfigUtil.getParameter();を呼び出さない

一度しか呼び出さないので、これで高速化が図れます。
しかし、同じようなことを使っているところすべて(もちろんボトルネックになっているところだけ)にやる必要があるため、
DRY(Don't Repeat Yourself)に違反しますね。

これは、ConfigUtil側で吸収すべき問題だと分かるはずです。
では
?ConfigUtilクラスを変更し、キャッシュするようにする
ではどうでしょう?

public final class ConfigUtil {
private static int param;
private static boolean isInit = false;
public static int getParameter() {
if (!isInit) {
// どこかから設定を取ってきてparamに代入
isInit = true;
}
return param;
}
}

これでキャッシュできるようになりますね。
これでもまあ問題ないでしょう。
しかし、データ取得とキャッシュ機能はまったく別物であり、
同じクラスに2つの責任があることに他なりません。
単一責任の原則(SRP)に違反しています。

処理を分けたいですね。しかも使用者には知られずに。
さて、ここでDecoratorパターンの登場です。

interface Config {
int getParameter();
}

class ConfigImpl implements Config {
public int getParameter() {
// どこかから設定を取ってくる
}
}

class ConfigCache implements Config {
private final Cinfig config;
private int param;
private boolean isInit = false;
ConfigCache(Config c) {
config = c;
}
public int getParameter() {
if (!isInit) {
param = config.getParameter();
isInit = true;
}
return param;
}
}

public final class ConfigUtil {
private static Config config = new ConfigCache(new ConfigImpl());
public static int getParameter() {
return config.getParameter();
}
}


ちょっと大掛かりになりました。
登場クラスを見てみます。
Configインターフェイス
ConfigImplクラス
ConfigCacheクラス
ConfigUtilクラス

ConfigImplとConfigCacheがConfigインターフェイスを継承しています。
ConfigImplはどこからか値を取ってくるクラスです。
ConfigCacheがDecoratorパターンなのですが、
キャッシュを保持するクラスです。
ConfigCacheクラスはコンストラクタでConfigインターフェイスを受け取り、
それに委譲する形で本処理を行い、付随処理(装飾)としてキャッシュの制御を行います。

ConfigUtilクラスは最初から出ていたクラスです。
フィールドにConfigインターフェイスを持ち、
そのインターフェイスに委譲するだけとしました。
フィールドの宣言は
private static Config config = new ConfigCache(new ConfigImpl());
として、ConfigImplクラスをConfigCacheクラスが包み込み、装飾するようにしてやることで、キャッシュ機能を追加できます。
キャッシュしなくてよい場合は、
private static Config config = new ConfigImpl();
とすればよいですね。

これがDecoratorパターンです。
JavaのFile系ライブラリで
new BufferedReader(new FileReader("file.txt"));
としているのがDecoratorパターンなんですね。
BufferedReaderがキャッシュを実現しています。


ユーティリティクラスに対してDecoratorパターンを使うと複雑になってしまいますが、通常はBufferedReaderの様にstaticでないクラスに対して適用すべきでしょう。