デコレーターパターン
CoddyのJavaジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 72/87。
Decorator Patternは、decoratorと呼ばれる特別なオブジェクトでオブジェクトをラップすることにより、新しい振る舞いを付加できる構造設計パターンです。コンパイル時に振る舞いを追加する継承とは異なり、decoratorは元のclassを変更せずに、実行時に動的に機能を追加します。
基本的なコーヒーから始め、ミルク、砂糖、ホイップクリームなどの追加を加えられるコーヒーショップを想像してください。追加するたびに元のコーヒーを包み、その料金と説明を加えます。このパターンでは共通のインターフェースを使用するため、decorated オブジェクトを元のオブジェクトと同じように扱えます。
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;
}
}decorator はラップされたオブジェクトへの参照を保持し、独自の動作を追加しながら呼び出しをそのオブジェクトに委譲します。複数の decorator を積み重ねて機能を組み合わせることができます。
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.75Decorator Pattern は、サブクラスが爆発的に増えることなくオブジェクトに責務を追加する必要がある場合に、その真価を発揮します。各 decorator は独立しているため、自由に組み合わせて、必要な動作を正確に実現する組み合わせを作成できます。
チャレンジ
簡単Decorator Pattern を使って通知システムを構築しましょう!それぞれの decorator を互いにラップすることで、異なる配信 channels、email alerts、SMS alerts、push notifications によって拡張できる基本通知を作成します。これは、1つのメッセージを複数の channels に同時に送信する必要がある実際のシステムを再現したものです。
コードを4つのファイルに分けて整理します。
Notifier.java: Decorator Pattern の component interface として機能するNotifierinterface を Define します。通知の送信を処理するsend(String message)メソッドを Declare します。BasicNotifier.java:Notifierを implements する concrete component class を Create します。これは最も単純な通知形式である in-app alert を表します。sendが呼び出されると、In-App: [message]を表示します。NotifierDecorators.java: ここで decorator hierarchy を構築します。まず、Notifierを implements し、ラップされたNotifierobject への参照を保持する abstractNotifierDecoratorclass から始めます。この参照は constructor を通じて渡されます。次に、
NotifierDecoratorを extend する3つの concrete decorator を作成します。EmailDecorator-sendが呼び出されると、まずラップされた notifier に処理を委譲し、その後Email: [message]を表示します。SMSDecorator-sendが呼び出されると、まずラップされた notifier に処理を委譲し、その後SMS: [message]を表示します。PushDecorator-sendが呼び出されると、まずラップされた notifier に処理を委譲し、その後Push: [message]を表示します。Main.java: decorator system を組み合わせます!2つの input を受け取ります。通知メッセージ(String)と、追加する channels の comma-separated list(String)です。たとえば、"email,sms"や"push,email,sms"です。BasicNotifierから Start します。その後、channels input に基づき、登場する順序で appropriate な decorators をラップします。有効な channel names はemail、sms、pushです。最後に、fully decorated notifier に対して、メッセージを使って
sendを Call します。
入力は順番に2つ受け取ります。送信するメッセージ(String)と、有効にする channels(String、comma-separated)です。
たとえば、入力が Server is down! と email,push の場合、出力は次のようになります。
In-App: Server is down!
Email: Server is down!
Push: Server is down!各 decorator が元の通知を維持しながら独自の動作を追加していることに注目してください。ラップする順序によって出力順が決まります。最も内側の component(BasicNotifier)が最初に実行され、次に各 decorator が自身の layer を追加します。組み合わせごとに個別の class を作成することなく、channels を自由に組み合わせることができます。
自分で試してみよう
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 に対して message で send を呼び出す
}
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4継承
継承の基本 (extends)super キーワードメソッドのオーバーライド (@Override)コンストラクタチェーンObject クラス単一継承と多段階継承なぜ多重継承ができないのかまとめ:従業員の階層構造自分で練習してみよう: Javaオンラインコンパイラ