デコレーターパターン
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 72/87。
デコレーターパターン(Decorator Pattern)は、オブジェクトをデコレーターと呼ばれる特別なオブジェクトでラップすることによって、そのオブジェクトに新しい振る舞いを追加できるようにする構造に関するデザインパターンです。コンパイル時に振る舞いを追加する継承とは異なり、デコレーターは元のクラスを修正することなく、実行時に動的に機能を追加します。
基本的なコーヒーから始めて、ミルク、砂糖、ホイップクリームなどのトッピングを追加できるコーヒーショップを想像してみてください。各追加要素は元のコーヒーを包み込み(ラップし)、そのコストと説明を追加します。このパターンは共通のインターフェースを使用するため、デコレーションされたオブジェクトも元のオブジェクトと同じように扱うことができます。
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}デコレータはラップされたオブジェクトへの参照を保持し、独自の振る舞いを追加しながら、そのオブジェクトに呼び出しを委譲します。複数のデコレータを積み重ねて、機能を組み合わせることも可能です。
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // シンプルなコーヒー、ミルク、砂糖
System.out.println(order.getCost()); // 2.75デコレーターパターンは、サブクラスを急増させることなくオブジェクトに責務を追加する必要がある場合に、その本領を発揮します。各デコレーターは独立しているため、自由に組み合わせて、必要な振る舞いの組み合わせを正確に作成することができます。
チャレンジ
簡単デコレーターパターン(Decorator Pattern)を使用して通知システムを構築しましょう!ベースとなる通知を作成し、デコレーターを重ねることで、メールアラート、SMSアラート、プッシュ通知といった異なる配信チャネルで機能を強化できるようにします。これは、1つのメッセージを複数のチャネルで同時に送信する必要がある現実世界のシステムを反映しています。
コードは4つのファイルに分けて構成します:
Notifier.java: デコレーターパターンのコンポーネントインターフェースとして機能するNotifierインターフェースを定義します。通知の送信を処理するsend(String message)メソッドを宣言する必要があります。BasicNotifier.java:Notifierを実装する具体的なコンポーネントクラスを作成します。これは最も単純な形式の通知であるアプリ内アラートを表します。sendが呼び出されたとき、"In-App: " + messageを出力するようにします。NotifierDecorators.java: ここでデコレーターの階層を構築します。まず、Notifierを実装し、ラップされたNotifierオブジェクトへの参照(コンストラクタ経由で渡される)を保持する抽象クラスNotifierDecoratorを作成します。次に、
NotifierDecoratorを継承する3つの具体的なデコレーターを作成します:EmailDecorator-sendが呼び出されたとき、まずラップされた notifier に処理を委譲し、次に"Email: " + messageを出力します。SMSDecorator-sendが呼び出されたとき、まずラップされた notifier に処理を委譲し、次に"SMS: " + messageを出力します。PushDecorator-sendが呼び出されたとき、まずラップされた notifier に処理を委譲し、次に"Push: " + messageを出力します。Main.java: デコレーターシステムを統合します!2つの入力を受け取ります:通知メッセージ(String)と、追加するチャネルのカンマ区切りリスト(String)です。例えば、"email,sms"や"push,email,sms"などです。まず
BasicNotifierから始めます。次に、入力されたチャネルに基づいて、出現順に適切なデコレーターでラップします。有効なチャネル名は"email"、"sms"、"push"です。最後に、完全にデコレートされた notifier に対して、メッセージを指定して
sendを呼び出します。
入力は、送信するメッセージ(String)と、有効にするチャネル(String、カンマ区切り)の順で受け取ります。
例えば、入力が "Server is down!" と "email,push" の場合、出力は以下のようになります:
In-App: Server is down!
Email: Server is down!
Push: Server is down!各デコレーターが元の通知を保持しつつ、独自の動作を追加していることに注目してください。ラップする順序が出力の順序を決定します。最も内側のコンポーネント(BasicNotifier)が最初に実行され、次に各デコレーターがそのレイヤーを追加します。組み合わせごとに個別のクラスを作成することなく、任意のチャネルの組み合わせを自由に構成できます!
チートシート
デコレーターパターン(Decorator Pattern)は、オブジェクトをデコレーターオブジェクトでラップすることにより、新しい振る舞いをアタッチする構造的なデザインパターンです。元のクラスを変更することなく、実行時に動的に機能を追加します。
このパターンは共通のインターフェースを使用するため、デコレートされたオブジェクトは元のオブジェクトと同じように扱うことができます。
基本構造
コンポーネントインターフェースを定義します:
interface Coffee {
String getDescription();
double getCost();
}インターフェースを実装する具体的なコンポーネントを作成します:
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}同じインターフェースを実装し、ラップされたオブジェクトへの参照を保持する抽象デコレータークラスを作成します:
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}抽象デコレーターを拡張する具体的なデコレーターを作成します:
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}デコレーターの使用
複数のデコレーターを積み重ねて機能を組み合わせます:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Simple Coffee, Milk, Sugar
System.out.println(order.getCost()); // 2.75デコレーターは、独自の振る舞いを追加しながら、ラップされたオブジェクトに呼び出しを委譲します。各デコレーターは独立しており、自由に組み合わせて使用できます。
自分で試してみよう
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// 送信するメッセージを読み込む
String message = scanner.nextLine();
// カンマ区切りのチャンネルリストを読み込む (例: "email,sms" または "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: BasicNotifier から開始する
// TODO: チャンネルの入力をカンマで分割する
// TODO: 各チャンネルをループで処理し、適切なデコレータで notifier をラップする
// 有効なチャンネルは "email"、"sms"、"push" です
// TODO: 完全にデコレートされた notifier の send をメッセージとともに呼び出す
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造