Help us understand the problem. What is going on with this article?

JavaでDecoratorパターン

More than 1 year has passed since last update.

はじめに

GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。

Decoratorパターン

Decoratorとは

日本語に訳すと「装飾者」を意味します。
元となるオブジェクトにどんどんデコレート(装飾)を行うことで機能の拡張を行うようなパターンのことをDecoratorパターンと言います。
このパターンを適用することで、拡張元となるクラスに変更を加えることなく、柔軟に機能の拡張を行うことができます。

登場人物

Decoratorパターン使用するのは以下のクラス図に登場するクラスです。
image.png

抽象クラス

  • Component

    装飾(拡張)される対象の元となる抽象クラスを表します。

  • Decorator

    Componentクラスを継承する抽象クラスで、装飾を行う側のクラスの元となるクラスです。 特筆すべき点として、装飾する対象であるComponentクラスをフィールドに持っている点が挙げられます。

実装クラス

  • ConcreteComponent
    Componentクラスの実装クラスです。Componentクラスの持つ抽象メソッドを全て実装します。

  • ConcreteDecorator
    Decoratorクラスの実装クラスです。DecoratorクラスはComponentクラスを継承しているため、Componentクラスの持つ抽象メソッドを実装する必要があります。

具体例

具体例として、以下のクラスをもとに説明します。
image.png

抽象クラス

  • SpongeCakeクラス
SpongeCake.java
public abstract class SpongeCake {
    public abstract String getName(); // 名前を得る

    public abstract int getPrice(); // 価格を得る

    public void show() { // 名前と価格を表示する
        System.out.println(getName() + ":" + getPrice() + "円");
    }
}

SpongeCakeクラスでは抽象メソッドとして名前と価格の取得を行うメソッドを定義し、具象メソッドとしてそれらの表示を行うメソッドを定義しています。
ただのスポンジケーキで何の飾り付けもされていないイメージです。
抽象クラスの実際の処理については後述のサブクラスで実装します。
特段難しい点はありません。

  • Decoratorクラス
Decorator.java
public abstract class Decorator extends SpongeCake {
    protected SpongeCake spongeCake; // この飾り枠がくるんでいる「中身」を指す

    protected Decorator(SpongeCake spongeCake) { // インスタンス生成時に「中身」を引数で指定
        this.spongeCake = spongeCake;
    }
}

Decoratorクラスは上述のSpongeCakeクラスを継承していますが、抽象メソッドのオーバーライドは行っていないため抽象クラスです。
装飾の対象であるSpongeCakeオブジェクトをフィールドに持ち、コンストラクタでオブジェクトを生成する時の引数に指定している点がポイントです。

実装クラス

  • ShortCakeクラス
ShortCake.java
public class ShortCake extends SpongeCake {

    @Override
    public String getName() { // 親クラスの名前を得るメソッドをオーバーライド
        return "ショートケーキ";
    }

    @Override
    public int getPrice() { // 親クラスの価格を得るメソッドをオーバーライド
        return 500;
    }

}
  • ChocolateCakeクラス
ChocolateCake.java
public class ChocolateCake extends SpongeCake {

    @Override
    public String getName() { // 親クラスの名前を得るメソッドをオーバーライド
        return "チョコレートケーキ";
    }

    @Override
    public int getPrice() { // 親クラスの価格を得るメソッドをオーバーライド
        return 700;
    }

}

ShortCakeクラス、ChocolateCakeクラスはSpongeCakeクラスの実装クラスです。
スポンジだけのケーキに少し飾り付けを行い、より具体的なケーキにしたイメージです。
抽象メソッドであるgetName()メソッド、getPrice()メソッドをオーバーライドしており、それぞれ名前と価格を返しています。
ここでも特段難しい点はありません。

  • StrawberryDecoratorクラス
StrawberryDecorator.java
public class StrawberryDecorator extends Decorator {

    public StrawberryDecorator(SpongeCake spongeCake) { // コンストラクタで飾り付けの対象を渡す
        super(spongeCake);
    }

    @Override
    public String getName() { // 中身の名前の先頭にイチゴを加えたもの
        return "イチゴ" + spongeCake.getName();
    }

    @Override
    public int getPrice() { // 中身の価格に100を加えたもの
        return spongeCake.getPrice() + 100;
    }

}
  • BananaDecoratorクラス
BananaDecorator.java
public class BananaDecorator extends Decorator {

    public BananaDecorator(SpongeCake spongeCake) { // コンストラクタで飾り付けの対象を渡す
        super(spongeCake);
    }

    @Override
    public String getName() { // 中身の名前の先頭にバナナを加えたもの
        return "バナナ" + spongeCake.getName();
    }

    @Override
    public int getPrice() { // 中身の価格に300を加えたもの
        return spongeCake.getPrice() + 300;
    }

}

StrawberryDecoratorクラス、BananaDecoratorクラスはDecoratorクラスの実装クラスです。
それぞれ飾り付けとしてコンストラクタで指定されたスポンジケーキ(またはそのサブクラス)にイチゴとバナナを加えます。
また、価格についても同様に追加します。

ポイントはコンストラクタの引数がSpongeCake型となっている点です。
このことからコンストラクタに渡せる引数はSpongeCakeクラス、またはそのサブクラスであるShortCakeクラス、ChocolateCakeクラスであることが分かります。
また、忘れてはならないのがDecoratorクラスもSpongeCakeクラスのサブクラスであるということです。
つまり装飾を行うStrawberryDecoratorクラスのコンストラクタの引数には、Decoratorクラスを継承しているStrawberryDecoratorクラス、BananaDecoratorクラスを指定可能であるということです。
これにより装飾された対象に対して更に装飾を加えるということが可能になります。

実行クラス

  • Mainクラス
Main.java
public class Main {
    public static void main(String[] args) {
        // ショートケーキの生成
        SpongeCake s1 = new ShortCake();
        SpongeCake s2 = new StrawberryDecorator(s1);
        SpongeCake s3 = new BananaDecorator(s1);
        SpongeCake s4 = new StrawberryDecorator(new ShortCake());
        SpongeCake s5 = new StrawberryDecorator(new BananaDecorator(new ShortCake()));
        s1.show();
        s2.show();
        s3.show();
        s4.show();
        s5.show();
        System.out.println("--------------------------------------------");
        // チョコレートケーキの生成
        SpongeCake c1 = new ChocolateCake();
        SpongeCake c2 = new StrawberryDecorator(c1);
        SpongeCake c3 = new BananaDecorator(c1);
        SpongeCake c4 = new StrawberryDecorator(new ChocolateCake());
        SpongeCake c5 = new StrawberryDecorator(new BananaDecorator(new ChocolateCake()));
        c1.show();
        c2.show();
        c3.show();
        c4.show();
        c5.show();
    }
}

ShortCakeクラスとChocolateCakeクラスのインスタンスを生成し、それぞれ装飾を行っています。
ポイントはStrawberryDecoratorクラス、BananaDecoratorクラスの説明でも述べたように、コンストラクタの引数に装飾の対象として装飾を行うクラスが指定可能であるため、複数の装飾を行うことが可能であるという点です。

また、

4,5行目
SpongeCake s1 = new ShortCake();
SpongeCake s2 = new StrawberryDecorator(s1);

7行目
SpongeCake s4 = new StrawberryDecorator(new ShortCake());

は装飾の対象のShortCakeをコンストラクタに指定しているだけで、書き方は違いますが実際には同じ処理です。

実行結果

Main.javaを実行した結果は以下になります。
元となるShortCakeとChocolateCakeにどんどんと装飾がなされていることが確認できます。

実行結果
ショートケーキ:500円
イチゴショートケーキ:600円
バナナショートケーキ:800円
イチゴショートケーキ:600円
イチゴバナナショートケーキ:900円
--------------------------------------------
チョコレートケーキ:700円
イチゴチョコレートケーキ:800円
バナナチョコレートケーキ:1000円
イチゴチョコレートケーキ:800円
イチゴバナナチョコレートケーキ:1100円

メリット

Decoratorパターンのメリットは以下になります。
1.継承元となるクラスに変更を加えずに機能の追加ができる
2.必要な部品を組み合わせることによって多様な機能を追加することができる

まとめ

オブジェクトに次々と装飾を重ねていくDecoratorパターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。

また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。

参考文献

mk777
SIer→Webに転職したエンジニア。学んだことなどをまとめていきます。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away