2010年6月13日日曜日

Decoratorパターン

Decoratorパターンとは、あるものを土台にしてDecorateする(飾り付ける)ためのパターンです。普通は飾り付けるといいますか、土台に対して何かを付加したい場合はその土台クラスを継承したクラスを実装することが考えられます。しかし、これですと臨機応変に付加したいタイプを変えたり、複数のタイプを付加したい場合に実現できません。「ベースはこれで、これとこれを付加したい、飾りつけたい」と言った場合に威力を発揮するのがこのパターンです。


今回は文字列を#または<>、あるいはその両方をつけてコンソールに表示するサンプルにします。

まず、文字列表示用のベースクラスです。
public abstract class SampleBase {
    public abstract String getValue();
    public final void print() {
        System.out.println(getValue());
    }
}
print()が呼ばれると子クラスで持っている文字列を表示します。もちろん飾りはついていれば飾りも合わせて表示されます。

文字列を持っているクラスは以下の感じです。
public class Sample extends SampleBase {
    private String value;
    public Sample(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}
コンストラクタを介して文字列が渡されます。SampleBaseを継承しているので、getValue()を実装しています。ただ単にこのクラスが管理している文字列を返すだけです。

次に、飾り付け用のベースクラスです。
public abstract class Decor extends SampleBase {
    protected SampleBase sampleBase;
    public Decor(SampleBase sampleBase) {
        this.sampleBase = sampleBase;
    }
}
飾り付けも表示されるのでSampleBaseを継承しているのが特徴です。そして、自身は何に対して飾り付けるのかを知っておく必要がありますので、sampleBaseを持っています。

具体的な飾り付けクラスの一つは以下のとおりです。
public class SharpDecor extends Decor {
    public SharpDecor(SampleBase sampleBase) {
        super(sampleBase);
    }

    public String getValue() {
        return "#" + sampleBase.getValue() + "#";
    }
}
コンストラクタを介して、飾り付けの対象を受け取ります。Sampleが渡されることがあるのは分かると思いますが、Decorを継承したオブジェクトが渡されることもあります。そして、そのDecor継承クラスは飾り付けの対象を知っていて・・・という風に構造が再帰的になります。これがDecoratorパターンの特徴です。ちなみに、SampleBaseもDecorもabstractなので、getValueメソッドはここで実装します。ここでは、飾り付けの対象からgetvalueメソッドで値を取り出し、#で「飾り付け」をして返しています。

他の飾り付けクラスは以下のとおりです。
public class BracketDecor extends Decor {
    public BracketDecor(SampleBase sampleBase) {
        super(sampleBase);
    }

    public String getValue() {
        return "<" + sampleBase.getValue() + ">";
    }
}
Mainは以下のとおりです。
public class Main {
    public static void main(String[] args) {
        SampleBase sample = new Sample("sample");
        sample.print(); // 何も飾りつけずに表示
        SampleBase sample2 = new SharpDecor(sample);
        sample2.print(); // #で飾り付け
        SampleBase sample3 = new BracketDecor(sample);
        sample3.print(); // <>で飾り付け
        SampleBase sample4 = new BracketDecor(sample2);
        sample4.print(); // #で飾りつけたサンプルに<>で飾り付け
    }
}
一番最初にSampleインスタンスを作り、まずは何も飾り付けずに表示します。その後に#で飾り付けた場合、<>で飾り付けた場合を表示し、最後に#で飾り付けたsample2を更に<>で飾り付けるという流れになっています。

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







このパターンを利用することで、機能追加が容易(今回の例でいう飾り付けの種類がいくらでも増やせる)であり、しかも、飾り付けられるもの(今回だとSampleクラス)を変更する必要がありません。文字列を表示すること自体も基本的にはsampleBaseへ移譲しているだけということもあり、クラス間結合(今回だとSampleとDecor、Decor同士)は緩い→動的に機能追加、差し替えができるのも特徴です。

ちなみに、java.ioパッケージはDecoratorパターンで実装されています。

関連パターン
Adapterパターン」中身のインターフェースを変えずに作るのがDecoratorパターンで、異なるインターフェースをつなぐのがAdapterパターン
Strategyパターン」飾りを変えたり重ねたりするのがDecoratorパターンで、アルゴリズムを変えるのがStrategyパターン

0 件のコメント:

コメントを投稿