こんにちは。KOUKIです。
以前、デコレーターパターンについて記事を書きましたが、別パターンでも実装したので紹介します。
<目次>
デコレーターパターン
デコレーターパターンは、あるコアとなる機能に柔軟に機能を追加していくことができるパターンです。まさにデコレーター(装飾)と呼ぶにふさわしいです。
実装
下記のコードを見てください。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
package decorator import "errors" type ingredientAdd interface { AddIngredient() (string, error) } // 1 type PizzaDecorator struct { ingredient ingredientAdd } func (p *PizzaDecorator) AddIngredient() (string, error) { return "", errors.New("Not implemented yet") } // 2 type Meat struct { Ingredient ingredientAdd } func (m *Meat) AddIngredient() (string, error) { return "", errors.New("Not implemented yet") } // 3 type Onion struct { Ingredient ingredientAdd } func (o *Onion) AddIngredient() (string, error) { return "", errors.New("Not implemented yet") } |
これは、Pizzaをイメージしてください。

ベースとなる生地の上に肉や玉ねぎをトッピングします。これがデコレーターのイメージです。
テストコード
まだ実装の途中ですが、テストコードを書いていきましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
package decorator import ( "strings" "testing" ) func TestPizzaDecorator_AddIngredient(t *testing.T) { pizza := &PizzaDecorator{} pizzaResult, _ := pizza.AddIngredient() expectedText := "Pizza with the following ingredients:" if !strings.Contains(pizzaResult, expectedText) { t.Fail() } } |
AddIngredientメソッドの戻り値のテストを実装しました。
しかし、当然ながらテストは失敗します。
1 2 3 4 5 6 |
$ go test -v -run=TestPizzaDecorator_AddIngredient === RUN TestPizzaDecorator_AddIngredient --- FAIL: TestPizzaDecorator_AddIngredient (0.00s) FAIL exit status 1 FAIL decorator 0.175s |
次に、以下のテストコードを実装します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
func TestMeat_AddIngredient(t *testing.T) { meat := &Meat{} meatResult, err := meat.AddIngredient() if err == nil { t.Fail() } // デコレーター meat = &Meat{&PizzaDecorator{}} meatResult, err = meat.AddIngredient() if err != nil { t.Fail() } if !strings.Contains(meatResult, "meat") { t.Fail() } } func TestOnion_AddIngredient(t *testing.T) { onion := &Onion{} onionResult, err := onion.AddIngredient() if err == nil { t.Fail() } // デコレーター onion = &Onion{&PizzaDecorator{}} onionResult, err = onion.AddIngredient() if err != nil { t.Fail() } if !strings.Contains(onionResult, "onion") { t.Fail() } } |
これは、デコレーターの挙動を知るのにちょうど良いコードです。
Meat/Onionの元々の機能にPizzaDecoratorで機能を追加しています。
もちろんテストは失敗しますが、実行してみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ go test -v -run=TestMeat_AddIngredient === RUN TestMeat_AddIngredient --- FAIL: TestMeat_AddIngredient (0.00s) FAIL exit status 1 FAIL decorator 0.908s $ go test -v -run=TestOnion_AddIngredient === RUN TestOnion_AddIngredient --- FAIL: TestOnion_AddIngredient (0.00s) FAIL exit status 1 FAIL decorator 0.657s |
AddIngredientの実装
AddIngredientメソッドの実装の続きをしましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
func (p *PizzaDecorator) AddIngredient() (string, error) { return "Pizza with the following ingredients:", nil } func (m *Meat) AddIngredient() (string, error) { if m.Ingredient == nil { return "", errors.New("an ingredientAdd is needed in the Ingredient field of the Meat") } s, err := m.Ingredient.AddIngredient() if err != nil { return "", err } return fmt.Sprintf("%s %s,", s, "meat"), nil } func (o *Onion) AddIngredient() (string, error) { if o.Ingredient == nil { return "", errors.New("an ingredientAdd is needed in the Ingredient field of the Onion") } s, err := o.Ingredient.AddIngredient() if err != nil { return "", err } return fmt.Sprintf("%s %s,", s, "onion"), nil } |
テストを実行します。
1 2 3 4 5 6 7 8 9 |
$ go test -v === RUN TestPizzaDecorator_AddIngredient --- PASS: TestPizzaDecorator_AddIngredient (0.00s) === RUN TestMeat_AddIngredient --- PASS: TestMeat_AddIngredient (0.00s) === RUN TestOnion_AddIngredient --- PASS: TestOnion_AddIngredient (0.00s) PASS ok decorator 0.171s |
OKですね。
おわりに
デコレーターパターンは、使い所がたくさんありそうなパターンですね。
機能拡張が予想される処理は、以下のようにインターフェースにして抽象化すると密結合を避けた実装ができます。
1 2 3 |
type ingredientAdd interface { AddIngredient() (string, error) } |
メソッドを実装するときは、このような考え方を取り入れることも有効ですね^^
それでは、また!
Go記事まとめ
ソースコード
decorator.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package decorator import ( "errors" "fmt" ) type ingredientAdd interface { AddIngredient() (string, error) } // 1 type PizzaDecorator struct { ingredient ingredientAdd } func (p *PizzaDecorator) AddIngredient() (string, error) { return "Pizza with the following ingredients:", nil } // 2 type Meat struct { Ingredient ingredientAdd } func (m *Meat) AddIngredient() (string, error) { if m.Ingredient == nil { return "", errors.New("an ingredientAdd is needed in the Ingredient field of the Meat") } s, err := m.Ingredient.AddIngredient() if err != nil { return "", err } return fmt.Sprintf("%s %s,", s, "meat"), nil } // 3 type Onion struct { Ingredient ingredientAdd } func (o *Onion) AddIngredient() (string, error) { if o.Ingredient == nil { return "", errors.New("an ingredientAdd is needed in the Ingredient field of the Onion") } s, err := o.Ingredient.AddIngredient() if err != nil { return "", err } return fmt.Sprintf("%s %s,", s, "onion"), nil } |
decorator_test.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
package decorator import ( "strings" "testing" ) func TestPizzaDecorator_AddIngredient(t *testing.T) { pizza := &PizzaDecorator{} pizzaResult, _ := pizza.AddIngredient() expectedText := "Pizza with the following ingredients:" if !strings.Contains(pizzaResult, expectedText) { t.Fail() } } func TestMeat_AddIngredient(t *testing.T) { meat := &Meat{} meatResult, err := meat.AddIngredient() if err == nil { t.Fail() } // デコレーター meat = &Meat{&PizzaDecorator{}} meatResult, err = meat.AddIngredient() if err != nil { t.Fail() } if !strings.Contains(meatResult, "meat") { t.Fail() } } func TestOnion_AddIngredient(t *testing.T) { onion := &Onion{} onionResult, err := onion.AddIngredient() if err == nil { t.Fail() } // デコレーター onion = &Onion{&PizzaDecorator{}} onionResult, err = onion.AddIngredient() if err != nil { t.Fail() } if !strings.Contains(onionResult, "onion") { t.Fail() } } |
コメントを残す
コメントを投稿するにはログインしてください。