Decoratorパターン
CoddyのGOジャーニー「オブジェクト指向プログラミング」セクションの一部 — レッスン 93/107。
Decoratorパターンは、オブジェクトを他のオブジェクトでラップすることにより、動的に新しい振る舞いを追加することを可能にします。Adapterはインターフェースを別のものに適合するように変更しますが、Decoratorは同じインターフェースを維持しつつ、元のオブジェクトの周囲にラッパーを重ねることで機能を強化します。
Goでは、基本型とデコレータの両方に同じインターフェースを実装させることで、これを実現します:
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は、コードを修正することなくオブジェクトに責務を追加する必要がある場合、特に機能のさまざまな組み合わせが必要な場合に最適です。これは、ロギング、キャッシュ、認証、および圧縮レイヤーによく使用されます。
チャレンジ
簡単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)を読み取り、現在の飲料を適切なデコレータでラップします。すべてのトッピングを適用した後、最終的な飲み物の説明とコストを別々の行に出力します。コストは
$X.XXの形式で、小数点以下2桁まで表示してください。
以下の入力が提供されます:
- 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各デコレータが前の飲料をラップし、説明とコストの両方を積み上げていく様子に注目してください。同じトッピングを複数回追加することもでき(ダブルミルクなど)、デコレータの順序が説明の順序を決定します。ベースのコーヒーはトッピングについて何も知りません。各デコレータは、自分がラップしているものを単に強化するだけです!
チートシート
Decorator パターンは、オブジェクトを他のオブジェクトでラップすることにより、動的に新しい振る舞いを追加します。同じインターフェースを維持しながら、レイヤー化を通じて機能を強化します。
基本型とデコレータの両方が同じインターフェースを実装します:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}デコレータは別のオブジェクトをラップし、委譲の前後に振る舞いを追加します:
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))
}複数のデコレータを積み重ねて、振る舞いを組み合わせることができます:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Sending: Hello
fmt.Println(withBoth.Send("Hello"))
// SENDING: [2024-01-15] HELLOオブジェクトのコードを変更せずに責任を追加するために Decorator を使用します。特に、さまざまな機能の組み合わせに有効です。一般的な用途:ロギング、キャッシュ、認証、圧縮。
自分で試してみよう
package main
import (
"fmt"
)
func main() {
// 追加トッピングの数を読み込む
var count int
fmt.Scanln(&count)
// ベースとなるコーヒーから開始する
var drink Beverage = &Coffee{}
// 各トッピングを読み込み、適切なデコレータでドリンクをラップする
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