Decoratorパターン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部。レッスン 93/107。
Decoratorパターンを使うと、オブジェクトを別のオブジェクトでラップすることで、新しい振る舞いを動的に追加できます。Adapterがインターフェースを別のインターフェースに合わせて変更するのに対し、Decoratorは同じインターフェースを維持しながら、元のオブジェクトの周囲にラッパーを重ねることで機能を拡張します。
Goでは、base type と decorator の両方に同じ interface を実装させることで、これを実現します。
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}デコレーターは別の Notifier をラップし、それに委譲する前後に動作を追加します。
type TimestampDecorator struct {
wrapped Notifier
}
func (t TimestampDecorator) Send(message string) string {
timestamped := "[2024-01-15] " + message
return t.wrapped.Send(timestamped)
}
type UppercaseDecorator struct {
wrapped Notifier
}
func (u UppercaseDecorator) Send(message string) string {
return strings.ToUpper(u.wrapped.Send(message))
}Decorator の強みは、複数のラッパーを積み重ねて動作を組み合わせられることです:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// 送信: Hello
fmt.Println(withBoth.Send("Hello"))
// 送信: [2024-01-15] HELLODecoratorは、オブジェクトのコードを変更せずに責務を追加する必要がある場合、特に機能のさまざまな組み合わせが必要な場合に最適です。これは、Logging、キャッシュ、認証、圧縮レイヤーによく使用されます。
チャレンジ
簡単Decoratorパターンを使って、コーヒーショップの注文システムを構築しましょう!基本となるコーヒードリンクと、ミルク、砂糖、ホイップクリームなどの追加要素を加えるデコレーターを作成します。各デコレーターは前のデコレーターをラップし、説明と価格の両方を積み重ねていきます。これは、デコレーターが機能を重ねていく仕組みを示す、現実世界での完璧な例です。
コードを3つのファイルに分けて整理します。
beverage.go:コアインターフェースと基本のコーヒー型を定義します。2つのメソッドを持つ
Beverageインターフェースを作成します。Description() string:ドリンクの内容を返しますCost() float64:価格を返します
基本の飲み物として
Coffee構造体を実装します。説明はCoffee、価格は2.00にします。decorators.go:任意の飲み物に追加要素を加えるデコレーター型を作成します。各デコレーターは
Beverageをラップし、同じインターフェースを実装します。3つのデコレーターを作成します。MilkDecorator:説明に, Milkを、価格に0.50を追加しますSugarDecorator:説明に, Sugarを、価格に0.25を追加しますWhipDecorator:説明に, Whipを、価格に0.75を追加します
各デコレーターはラップした飲み物のメソッドを呼び出し、その結果を拡張します。
main.go:デコレーターを積み重ねて、カスタマイズしたドリンクを構築します。追加する要素の数を読み取ります。次に各追加要素について、その種類(
milk、sugar、またはwhip)を読み取り、現在の飲み物を適切なデコレーターでラップします。すべての追加要素を適用した後、最終的なドリンクの説明と価格を別々の行に出力します。価格は小数点以下2桁ちょうどの
$X.XX形式で表示します。
次の入力が与えられます。
- 1行目:追加する要素の数
- 続く行:1行につき1つの追加要素の種類(
milk、sugar、またはwhip)
例えば、次の入力の場合:
2
milk
sugar出力は次のようになります。
Coffee, Milk, Sugar
$2.75また、次の入力の場合:
3
whip
milk
milk出力は次のようになります。
Coffee, Whip, Milk, Milk
$3.75さらに、次の入力の場合:
0出力は次のようになります。
Coffee
$2.00各デコレーターが前の飲み物をラップし、説明と価格の両方を積み重ねていくことに注目してください。同じ追加要素を複数回(ミルクを2倍にする場合など)追加でき、デコレーターの順序によって説明内の順序が決まります。基本のコーヒーは追加要素について何も知りません。各デコレーターは、ラップした対象をそのまま拡張するだけです。
自分で試してみよう
package main
import (
"fmt"
)
func main() {
// エクストラの数を読み取る
var count int
fmt.Scanln(&count)
// ベースのコーヒーから始める
var drink Beverage = &Coffee{}
// 各エクストラを読み取り、適切なデコレータでdrinkをラップする
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: エクストラの種類に基づいて、適切なデコレータでdrinkをラップする
// "milk"にはMilkDecoratorを使用する
// "sugar"にはSugarDecoratorを使用する
// "whip"にはWhipDecoratorを使用する
}
// TODO: 最終的な説明とコストを出力する
// コストを小数点以下ちょうど2桁の$X.XX形式でフォーマットする
}
このレッスンには短いクイズがあります。レッスンを始めて解答し、進捗を記録しましょう。
オブジェクト指向プログラミングのすべてのレッスン
1Go オブジェクト指向の基礎
外部ファイルGo ワークスペースとモジュールパッケージとインポート公開された名前と非公開の名前Go におけるオブジェクト指向入門クラスとしての構造体構造体へのメソッド定義ポインタレシーバと値レシーバ構造体の初期化コンストラクタ関数まとめ:簡易計算機8エラー処理とOOP
error インターフェースカスタムエラー型エラーラッピング (fmt.Errorf)センチネルエラーerrors.Is() と errors.As()Panic、Defer、Recover復習 - ファイルパーサー11標準ライブラリとOOP
io.Reader と io.Writersort.Interfacefmt.Stringer インターフェースencoding/json と構造体http.Handler インターフェースまとめ:REST API モデル14デザインパターン パート2
CommandパターンAdapterパターンDecoratorパターンTemplate MethodパターンStateパターンCompositeパターンDecoratorとしてのMiddleware自分で練習してみよう: Goオンラインコンパイラ