デザインパターン入門 | Decorator(デコレーター)パターン
「Decorator(デコレーター)」パターンは、、インスタンスを「デコレーション(Decoration)」していくことで、インスタンスにさまざまな機能を追加することができるデザインパターンです。
継承ではなく、「委譲」によって階層構造を形成していき、インスタンス間の関係を構築していきます。
それでは、「Decorator(デコレーター)」パターンの仕組みと事例について見ていきましょう。
「Decorator(デコレーター)」パターンの仕組み
「Decorator(デコレーター)」パターンのクラス構造は、慣れるまで少し複雑に感じられるかもしれません。
今回は「ピザを作る」という事例を通して、どのようなクラスを作っていくのかを学んでいきましょう。
それぞれのクラスと継承関係は下図のようになります。
「Dough」クラスは、「生地」を表す抽象クラスです。
この抽象クラスの内容は下記のようになります。
public abstract class Dough { public abstract void displayDoughType(); }
この抽象クラスには、
- displayDoughType
- 生地の内容を表示するメソッド
が含まれています。
そして、「ピザ生地」を表す「PizzaDough」クラスは下記のようになります。
public class PizzaDough extends Dough{ private String doughType; public PizzaDough(String doughType) { this.doughType = doughType; } public void displayDoughType() { System.out.println("このピザの生地は「" + this.doughType + "」です。"); } }
このクラスは「Dough」クラスを継承していますが、「生地の種類」を保持するための「doughType」フィールドを定義し、コンストラクタで受け取った生地の種類を格納しています。
次に「ピザ生地に乗せる具材(トッピング素材)」を表した「ToppingMaterial」クラスを作ります。
public class ToppingMaterial extends Dough { protected Dough dough; protected String materialName; public ToppingMaterial(Dough dough, String materialName) { this.dough = dough; this.materialName = materialName; } public void displayMaterialName() { if ( this.dough instanceof ToppingMaterial ) { ((ToppingMaterial) dough).displayMaterialName(); } System.out.print( materialName ); } @Override public void displayDoughType() { System.out.println("生地の種類は:ピザ生地です。"); } }
このクラスには「Dough」クラス型のフィールド「dough」を定義していますが、「Decorator(デコレーター)」パターンでは、このフィールドは重要な役割を担っています。
「dough」にはコンストラクタによって、渡されてきた「dough」クラスを継承したインスタンスをセットしています。
後ほど、このフィールドの値を使って「再帰呼び出し」を行いますので、この関係を覚えておいてください。
他にも、
- displayMaterialName
- 素材名を出力するメソッド
が定義されています。
このメソッドは後ほど、作成する「mainメソッド」で、再帰的に呼び出されるメソッドとなります。
次にピザのトッピング材料を表す「TomatoMaterial」クラスを作ります。
public class TomatoMaterial extends ToppingMaterial{ private String materilaName = "トマト"; public TomatoMaterial(Dough dough, String materialName) { super(dough, materialName); } }
といっても定義しているのは素材名を表す「materilaName」とコンストラクタのみとなっています。
もう一つのトッピング材料の「チーズ」を表す「CheeseMaterial」クラスは次のようになります。
public class CheeseMaterial extends ToppingMaterial { private String materilaName = "チーズ"; public CheeseMaterial(Dough dough, String materialName) { super(dough, materialName); } }
このクラスも「TomatoMaterial」クラスと同様の構造となっています。
これで、すべてのクラスが揃いましたが、これらのクラスのインスタンスがどのような動作をしているのかを見ていきましょう。
「main」メソッドは下記のようになります。
public class Main { public static void main(String[] args) { Dough pizzaDough = new PizzaDough("スーパークリスピー"); Dough P1 = new TomatoMaterial(pizzaDough, "トマト"); Dough P2 = new CheeseMaterial(P1, "カッテージチーズ"); // ピザ名の出力 System.out.print("このピザは、「"); ((ToppingMaterial) P2).displayMaterialName(); System.out.print("ピザ」です。"); } }
そして、出力結果は下記のようになります。
このピザは、「トマトカッテージチーズピザ」です。
ピザの名前は「displayMaterialName」メソッドが再帰的に呼び出されて出力されていますね。
「displayMaterialName」メソッドは、
public void displayMaterialName() { if ( this.dough instanceof ToppingMaterial ) { ((ToppingMaterial) dough).displayMaterialName(); } System.out.print( materialName ); }
のようになっています。
「dough」フィールドの値が「ToppingMaterial」クラスのインスタンスであるかを調べ、「ToppingMaterial」にキャスト後に、「displayMaterialName」メソッドを実行し、ピザの素材名を出力しています。
さらに「チキン」を追加でトッピングするため、「ChickenMaterial」クラスを作りました。
public class ChickenMaterial extends ToppingMaterial{ private String materilaName = "チキン"; public ChickenMaterial(Dough dough, String materialName) { super(dough, materialName); } }
そして、「main」メソッドにも「ChickenMaterial」クラスの生成処理を追加します。
public class Main { public static void main(String[] args) { Dough pizzaDough = new PizzaDough("スーパークリスピー"); Dough P1 = new TomatoMaterial(pizzaDough, "トマト"); Dough P2 = new CheeseMaterial(P1, "カッテージチーズ"); // 「テリヤキチキン」をピザのトッピングに追加 Dough P3 = new ChickenMaterial(P2, "テリヤキチキン"); // ピザ名の出力 System.out.print("このピザは、「"); ((ToppingMaterial) P3).displayMaterialName(); System.out.print("ピザ」です。"); } }
出力結果は、下記のようになります。
このピザは、「トマトカッテージチーズテリヤキチキンピザ」です。
このようにどんどんトッピングを増やしていっても、「displayMaterialName」メソッドを呼ぶとトッピングしたピザ名を出力することができます。
今回はピザをテーマに「デコレータパターン」についてご説明をしましたが、他にもさまざまな事例でデコレータパターンを利用することができますので、身近にデコレータパターンが使えるケースがないかを考えてみてもらえればと思います。