デコレーターパターン
CoddyのDartジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 96/110。
デコレーターパターン(Decorator pattern)を使用すると、オブジェクトをデコレーターと呼ばれる特殊なオブジェクトでラップすることで、新しい振る舞いを動的に追加できます。コンパイル時に振る舞いを追加する継承とは異なり、デコレーターは元のクラスを変更することなく、実行時に追加の責任を付与します。
重要な考え方は、元のオブジェクトとデコレータの両方が同じインターフェースを実装しているということです。
デコレータは元のオブジェクトをラップして処理を委譲し、その前後に独自の振る舞いを追加します。
abstract class Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
@override
String getDescription() => 'Simple Coffee';
@override
double getCost() => 2.0;
}
class MilkDecorator implements Coffee {
final Coffee _coffee;
MilkDecorator(this._coffee);
@override
String getDescription() => '${_coffee.getDescription()}, Milk';
@override
double getCost() => _coffee.getCost() + 0.5;
}
class SugarDecorator implements Coffee {
final Coffee _coffee;
SugarDecorator(this._coffee);
@override
String getDescription() => '${_coffee.getDescription()}, Sugar';
@override
double getCost() => _coffee.getCost() + 0.2;
}
void main() {
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);
print(coffee.getDescription()); // Simple Coffee, Milk, Sugar
print(coffee.getCost()); // 2.7
}各デコレータは前のオブジェクトをラップし、それを拡張します。複数のデコレータを任意の組み合わせで積み重ねることができ、考えられるすべての組み合わせに対してサブクラスを作成することなく、柔軟な動作を実現できます。このパターンは、同じクラスの他のオブジェクトに影響を与えることなく、オブジェクトに機能を追加する必要がある場合に最適です。
チャレンジ
簡単デコレーターパターン(Decorator pattern)を使用して、ピザ注文システムを構築しましょう!ベースとなるピザを作成し、そこにさまざまなトッピングを追加できるようにします。各トッピングは前のアイテムをラップし、説明と価格の両方を加算していきます。
コードは2つのファイルに分けて構成します:
pizza.dart: このファイルには、ピザのインターフェースとすべてのデコレータークラスが含まれます。まず、String getDescription()とdouble getPrice()の2つのメソッドを定義する抽象クラスPizzaを作成します。次に、Pizzaを実装するPlainPizzaクラスを作成します。これは説明として'Plain Pizza'を返し、価格は8.0です。次に、それぞれがPizzaをラップして機能を強化する3つのデコレータークラスを構築します:CheeseDecorator- 説明に', Cheese'を追加し、価格に1.5を追加しますPepperoniDecorator- 説明に', Pepperoni'を追加し、価格に2.0を追加しますMushroomDecorator- 説明に', Mushrooms'を追加し、価格に1.25を追加します
Pizzaを実装し、コンストラクタでPizzaを受け取り、自身の貢献分を追加する前にラップされたピザに処理を委譲する必要があります。main.dart: ピザファイルをインポートし、デコレーターを積み重ねてカスタマイズされた注文を作成する方法を示します。PlainPizzaから始め、それをCheeseDecoratorでラップし、さらにそれをPepperoniDecoratorでラップし、最後にすべてをMushroomDecoratorでラップします。最終的なピザの説明を出力し、次に'Total:に続けて価格を出力します。
各デコレーターが前のデコレーターの上にどのように構築されるかに注目してください。シンプルで単一責任のデコレーターを積み重ねることで、トッピングがたっぷりのピザを作成しています!
期待される出力:
Plain Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.75チートシート
Decoratorパターンは、オブジェクトをデコレータオブジェクトでラップすることにより、動的に新しい振る舞いを追加します。元のオブジェクトとデコレータの両方が同じインターフェースを実装します。
主な構成要素:
- 共通のメソッドを定義する抽象クラスまたはインターフェース
- 基本機能を実装する具体的なクラス
- 元のオブジェクトをラップして拡張するデコレータクラス
各デコレータ:
- 装飾するオブジェクトと同じインターフェースを実装する
- ラップされたオブジェクトへの参照を保持する
- ラップされたオブジェクトに呼び出しを委譲する
- 委譲の前後に独自の振る舞いを追加する
abstract class Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
@override
String getDescription() => 'Simple Coffee';
@override
double getCost() => 2.0;
}
class MilkDecorator implements Coffee {
final Coffee _coffee;
MilkDecorator(this._coffee);
@override
String getDescription() => '${_coffee.getDescription()}, Milk';
@override
double getCost() => _coffee.getCost() + 0.5;
}
// 複数のデコレータを積み重ねる
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);デコレータは任意の組み合わせで積み重ねることができ、考えられるすべての組み合わせに対してサブクラスを作成することなく、柔軟な振る舞いを作成できます。
自分で試してみよう
import 'pizza.dart';
void main() {
// TODO: PlainPizza を作成する
// TODO: それを CheeseDecorator でラップする
// TODO: さらにそれを PepperoniDecorator でラップする
// TODO: すべてを MushroomDecorator でラップする
// TODO: 最終的なピザの説明を表示する
// TODO: "Total: $" に続いて価格を表示する
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
4Null Safety
Null Safety 入門Nullable と Non-Nullable? 演算子と ! 演算子late キーワードと Null SafetyNull-Aware 演算子クラスにおける Null Safetyまとめ:ユーザープロフィールシステム10コレクションとジェネリクス
List, Set, Map の概要型安全なコレクションジェネリッククラスジェネリックメソッドジェネリクスの制約Iterable と Iteratorまとめ:ジェネリックなストレージ