はじめに
GoFのデザインパターンを紹介している『増補改訂版 Java言語で学ぶデザインパターン入門』を読んで、学んだ内容についてまとめます。
Decoratorパターン
Decoratorとは
日本語に訳すと「装飾者」を意味します。
元となるオブジェクトにどんどんデコレート(装飾)を行うことで機能の拡張を行うようなパターンのことをDecoratorパターンと言います。
このパターンを適用することで、拡張元となるクラスに変更を加えることなく、柔軟に機能の拡張を行うことができます。
登場人物
Decoratorパターン使用するのは以下のクラス図に登場するクラスです。
抽象クラス
-
Component
装飾(拡張)される対象の元となる抽象クラスを表します。 -
Decorator
Component
クラスを継承する抽象クラスで、装飾を行う側のクラスの元となるクラスです。 特筆すべき点として、装飾する対象であるComponent
クラスをフィールドに持っている点が挙げられます。
実装クラス
-
ConcreteComponent
Component
クラスの実装クラスです。Componentクラスの持つ抽象メソッドを全て実装します。 -
ConcreteDecorator
Decorator
クラスの実装クラスです。DecoratorクラスはComponentクラスを継承しているため、Component
クラスの持つ抽象メソッドを実装する必要があります。
具体例
抽象クラス
- SpongeCakeクラス
public abstract class SpongeCake {
public abstract String getName(); // 名前を得る
public abstract int getPrice(); // 価格を得る
public void show() { // 名前と価格を表示する
System.out.println(getName() + ":" + getPrice() + "円");
}
}
SpongeCake
クラスでは抽象メソッドとして名前と価格の取得を行うメソッドを定義し、具象メソッドとしてそれらの表示を行うメソッドを定義しています。
ただのスポンジケーキで何の飾り付けもされていないイメージです。
抽象クラスの実際の処理については後述のサブクラスで実装します。
特段難しい点はありません。
- Decoratorクラス
public abstract class Decorator extends SpongeCake {
protected SpongeCake spongeCake; // この飾り枠がくるんでいる「中身」を指す
protected Decorator(SpongeCake spongeCake) { // インスタンス生成時に「中身」を引数で指定
this.spongeCake = spongeCake;
}
}
Decoratorクラス
は上述のSpongeCakeクラスを継承していますが、抽象メソッドのオーバーライドは行っていないため抽象クラスです。
装飾の対象であるSpongeCakeオブジェクトをフィールドに持ち、コンストラクタでオブジェクトを生成する時の引数に指定している点がポイントです。
実装クラス
- ShortCakeクラス
public class ShortCake extends SpongeCake {
@Override
public String getName() { // 親クラスの名前を得るメソッドをオーバーライド
return "ショートケーキ";
}
@Override
public int getPrice() { // 親クラスの価格を得るメソッドをオーバーライド
return 500;
}
}
- ChocolateCakeクラス
public class ChocolateCake extends SpongeCake {
@Override
public String getName() { // 親クラスの名前を得るメソッドをオーバーライド
return "チョコレートケーキ";
}
@Override
public int getPrice() { // 親クラスの価格を得るメソッドをオーバーライド
return 700;
}
}
ShortCake
クラス、ChocolateCake
クラスはSpongeCake
クラスの実装クラスです。
スポンジだけのケーキに少し飾り付けを行い、より具体的なケーキにしたイメージです。
抽象メソッドであるgetName()
メソッド、getPrice()
メソッドをオーバーライドしており、それぞれ名前と価格を返しています。
ここでも特段難しい点はありません。
- StrawberryDecoratorクラス
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クラス
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クラス
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
クラスの説明でも述べたように、コンストラクタの引数に装飾の対象として装飾を行うクラスが指定可能であるため、複数の装飾を行うことが可能であるという点です。
また、
SpongeCake s1 = new ShortCake();
SpongeCake s2 = new StrawberryDecorator(s1);
と
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パターンに関して学びました。
以下でサンプルコードをアップしていますのでよろしければ参考にどうぞ。
また、他のデザインパターンに関しては以下でまとめていますので、こちらも参考にどうぞ。