1. パターンの意図
デコレータ(Decorator)パターン は、
既存のオブジェクトに動的に機能を追加できるようにする デザインパターンです。
解決する問題
- 継承で機能を拡張するとクラス数が爆発する
- オブジェクトごとに「必要な機能だけ」柔軟に追加したい
- 動的に入れ替え可能な機能拡張が欲しい
📌 ポイント
-
「包む(wrap)」ことで機能を拡張
-
組み合わせ自由に追加可能
-
実行時に差し込める柔軟性がある
2. UML 図
-
Component:共通インターフェース
-
ConcreteComponent:元の機能
-
Decorator:Component を保持し、機能を拡張
-
ConcreteDecorator:実際の拡張処理
3. Flutter / Dart 実装例
3.1 Component
abstract class Coffee {
String getDescription();
double cost();
}
3.2 ConcreteComponent
class SimpleCoffee implements Coffee {
@override
String getDescription() => "Simple Coffee";
@override
double cost() => 2.0;
}
3.3 Decorator
abstract class CoffeeDecorator implements Coffee {
final Coffee coffee;
CoffeeDecorator(this.coffee);
@override
String getDescription() => coffee.getDescription();
@override
double cost() => coffee.cost();
}
3.4 ConcreteDecorator
class MilkDecorator extends CoffeeDecorator {
MilkDecorator(Coffee coffee) : super(coffee);
@override
String getDescription() => "${super.getDescription()}, Milk";
@override
double cost() => super.cost() + 0.5;
}
class SugarDecorator extends CoffeeDecorator {
SugarDecorator(Coffee coffee) : super(coffee);
@override
String getDescription() => "${super.getDescription()}, Sugar";
@override
double cost() => super.cost() + 0.2;
}
3.5 利用例
void main() {
Coffee coffee = SimpleCoffee();
print("${coffee.getDescription()} : \$${coffee.cost()}");
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);
print("${coffee.getDescription()} : \$${coffee.cost()}");
}
出力:
Simple Coffee : $2.0
Simple Coffee, Milk, Sugar : $2.7
4. Android / Kotlin 実装例
4.1 Component
interface Coffee {
fun getDescription(): String
fun cost(): Double
}
4.2 ConcreteComponent
class SimpleCoffee : Coffee {
override fun getDescription() = "Simple Coffee"
override fun cost() = 2.0
}
4.3 Decorator
abstract class CoffeeDecorator(private val coffee: Coffee) : Coffee {
override fun getDescription(): String = coffee.getDescription()
override fun cost(): Double = coffee.cost()
}
4.4 ConcreteDecorator
class MilkDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun getDescription() = super.getDescription() + ", Milk"
override fun cost() = super.cost() + 0.5
}
class SugarDecorator(coffee: Coffee) : CoffeeDecorator(coffee) {
override fun getDescription() = super.getDescription() + ", Sugar"
override fun cost() = super.cost() + 0.2
}
4.5 利用例
fun main() {
var coffee: Coffee = SimpleCoffee()
println("${coffee.getDescription()} : $${coffee.cost()}")
coffee = MilkDecorator(coffee)
coffee = SugarDecorator(coffee)
println("${coffee.getDescription()} : $${coffee.cost()}")
}
5. メリット / デメリット
メリット
- 継承を使わずに動的に機能追加できる
- 必要な組み合わせだけで拡張可能
- 責務を小さなデコレーションに分割できる
デメリット
- ラップが多段になるとデバッグが複雑になる
- 設計を誤るとどこで何を追加しているか見えにくくなる
6. 実務ユースケース
Flutter
-
Widget の修飾(
Padding,Container,DecoratedBoxなど) - Stream/Provider のラップ(追加処理を注入)
- UI に機能を後付けするカスタムラッパ
Android (Kotlin)
-
Java I/O API(
BufferedInputStream,DataInputStream→ InputStream をデコレート) - RecyclerView.ItemDecoration(表示の装飾)
- OkHttp Interceptor(リクエスト/レスポンスをデコレート)
7. 実装上の注意点
- 「機能追加」と「状態変更」を混同しないこと
- デコレータは 本体を包んで責務を追加する のみに絞ると明快
- デバッグしやすいように ログ出力や階層確認を仕込むと安心
8. どんなときに使う?
- 継承でクラス爆発しそうなとき
- 動的に「必要な機能だけ追加」したいとき
- UI のレイヤー修飾、I/O ラップ、Interceptor のように 責務を差し込む場面
まとめ
- デコレータパターンは「オブジェクトを包んで機能を動的に追加する」
- Flutter では Widget、Android では Java I/O や RecyclerView に代表例
- 継承ではなく合成で拡張する柔軟性を得られる
- ただし多段ラップは追跡が難しくなるため、設計の見通しが大事