Menu
Coddy logo textTech

デコレーターパターン

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
}

各デコレータは前のオブジェクトをラップし、それを拡張します。複数のデコレータを任意の組み合わせで積み重ねることができ、考えられるすべての組み合わせに対してサブクラスを作成することなく、柔軟な動作を実現できます。このパターンは、同じクラスの他のオブジェクトに影響を与えることなく、オブジェクトに機能を追加する必要がある場合に最適です。

challenge icon

チャレンジ

簡単

デコレーターパターン(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: $" に続いて価格を表示する
}
quiz icon腕試し

このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。

オブジェクト指向プログラミングのすべてのレッスン