こんにちは。KOUKIです。
以前、Prototypeパターンについて記事を書きましたが、別パターンでも実装したので紹介します。

<目次>
Prototypeパターン
Prototypeパターンはインスタンスの生成に関するデザインパターンで、インスタンスからインスタンスをコピーする方法を提供しています。
サンプルコード
シャツに関するサンプルコードで、Prototypeパターンの理解を深めましょう。
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 |
<meta charset="utf-8">package prototype import "errors" const ( // ID White = 1 Black = 2 Blue = 3 ) type ItemInfoGetter interface { GetInfo() string } type ShirtCloner interface { GetClone(m int) (ItemInfoGetter, error) } func NewShirtsCloner() ShirtCloner { return &ShirtsCache{} } type ShirtColor byte type Shirt struct { Price float32 SKU string Color ShirtColor } func (s *Shirt) GetInfo() string { return fmt.Sprintf( "Shirt with SKU '%s' and color id %d that costs %f\n", s.SKU, s.Color, s.Price) } func (s *Shirt) GetPrice() float32 { return s.Price } <meta charset="utf-8">var whitePrototype *Shirt = &Shirt{ Price: 15.00, SKU: "empty", Color: White, } type ShirtsCache struct{} func (s *ShirtsCache) GetClone(id int) (ItemInfoGetter, error) { return nil, errors.New("Not implemented yet") } |
このコードでは、White、Black、Blueの3種類のシャツがあると明示されています。
NewShirtsCloner関数を実行するとShirtClonerインターフェースを実装したインスタンスを取得できます。
普通ならそれぞれの種類ごとにNewShirtsCloner関数を呼び出すことになると思いますが、Prototypeパターンではインスタンスをコピーして新しいインスタンスを作成するため、不要になります。
テストコード
実装はまだ途中ですが、テストコードで動きを確認してみましょう。
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 |
package prototype import "testing" func TestClone(t *testing.T) { shirtCache := NewShirtsCloner() if shirtCache == nil { t.Fatal("Not implemented yet") } item1, err := shirtCache.GetClone(White) if err != nil { t.Fatal(err) } if item1 == whitePrototype { t.Fatal("item1 connot be equal to the white prototype") } shirt1, ok := item1.(*Shirt) if !ok { t.Fatal("Type aassertion for shirt1 couldn't be done successfully") } shirt1.SKU = "abbcc" item2, err := shirtCache.GetClone(White) if err != nil { t.Fatal(err) } shirt2, ok := item2.(*Shirt) if !ok { t.Fatal("Type aassertion for shirt2 couldn't be done successfully") } if shirt1.SKU == shirt2.SKU { t.Error("SKU's of shirt1 and shirt2 must be different") } if shirt1 == shirt2 { t.Error("shirt1 cannot be equal to shirt2") } t.Logf("LOG - shirt1: %s", shirt1.GetInfo()) t.Logf("LOG - shirt2: %s", shirt2.GetInfo()) } |
前述の通り、NewShirtsClonerの呼び出しは一度のみです。それ以降は、GetCloneを使って種類ごとのシャツのコピーを取得しようとしています。
また、シャツのIDはWhiteを使っておりGetCloneの呼び出し時に引数として渡しています。これで、同じインスタンスを取得していないか(コピーされたインスタンスが返されるか)テストをするわけです。
テストを実行してみましょう。
1 2 3 4 5 6 |
$ go test -run=TestClone --- FAIL: TestClone (0.00s) prototype_test.go:13: Not implemented yet FAIL exit status 1 FAIL prototype 0.606s |
期待通り、エラーになりましたね。
インスタンス生成
GetCloneメソッドにインスタンスの生成ロジックを実装します。
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 |
var whitePrototype *Shirt = &Shirt{ Price: 15.00, SKU: "empty", Color: White, } var blackPrototype *Shirt = &Shirt{ Price: 16.00, SKU: "empty", Color: Black, } var bluePrototype *Shirt = &Shirt{ Price: 17.00, SKU: "empty", Color: Blue, } type ShirtsCache struct{} func (s *ShirtsCache) GetClone(id int) (ItemInfoGetter, error) { switch id { case White: newItem := *whitePrototype return &newItem, nil case Black: newItem := *blackPrototype return &newItem, nil case Blue: newItem := *bluePrototype return &newItem, nil default: return nil, errors.New("Shirt model not recognized") } } |
シャツのIDに応じてインスタンスをコピー(ポインタを使用)し、呼び出し元に返しています。
テストを実行します。
1 2 3 4 5 6 7 |
$ go test -run=TestClone -v === RUN TestClone prototype_test.go:38: LOG - shirt1: Shirt with SKU 'abbcc' and color id 1 that costs 15.000000 prototype_test.go:39: LOG - shirt2: Shirt with SKU 'empty' and color id 1 that costs 15.000000 --- PASS: TestClone (0.00s) PASS ok prototype 0.668s |
OKですね。
おわりに
このパターンはどんなときに使うんだ?と思いますよね。ちょっと使い道が思い浮かばないので、サンプルが悪かったかもしれません。
デザインパターンを意識しすぎて、読みにくいコードを書いてしまわないように、「何故、このデザインパターンを使うのか」ということを念頭に置いて実装していきたいですね。
それでは、また!
Go記事まとめ
ソースコード
prototype.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 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
package prototype import ( "errors" "fmt" ) const ( // ID White = 1 Black = 2 Blue = 3 ) type ItemInfoGetter interface { GetInfo() string } type ShirtCloner interface { GetClone(m int) (ItemInfoGetter, error) } func NewShirtsCloner() ShirtCloner { return &ShirtsCache{} } type ShirtColor byte type Shirt struct { Price float32 SKU string Color ShirtColor } func (s *Shirt) GetInfo() string { return fmt.Sprintf( "Shirt with SKU '%s' and color id %d that costs %f\n", s.SKU, s.Color, s.Price) } func (s *Shirt) GetPrice() float32 { return s.Price } var whitePrototype *Shirt = &Shirt{ Price: 15.00, SKU: "empty", Color: White, } var blackPrototype *Shirt = &Shirt{ Price: 16.00, SKU: "empty", Color: Black, } var bluePrototype *Shirt = &Shirt{ Price: 17.00, SKU: "empty", Color: Blue, } type ShirtsCache struct{} func (s *ShirtsCache) GetClone(id int) (ItemInfoGetter, error) { switch id { case White: newItem := *whitePrototype return &newItem, nil case Black: newItem := *blackPrototype return &newItem, nil case Blue: newItem := *bluePrototype return &newItem, nil default: return nil, errors.New("Shirt model not recognized") } } |
prototype_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 |
package prototype import "testing" func TestClone(t *testing.T) { shirtCache := NewShirtsCloner() if shirtCache == nil { t.Fatal("Not implemented yet") } item1, err := shirtCache.GetClone(White) if err != nil { t.Fatal(err) } if item1 == whitePrototype { t.Fatal("item1 connot be equal to the white prototype") } shirt1, ok := item1.(*Shirt) if !ok { t.Fatal("Type aassertion for shirt1 couldn't be done successfully") } shirt1.SKU = "abbcc" item2, err := shirtCache.GetClone(White) if err != nil { t.Fatal(err) } shirt2, ok := item2.(*Shirt) if !ok { t.Fatal("Type aassertion for shirt2 couldn't be done successfully") } if shirt1.SKU == shirt2.SKU { t.Error("SKU's of shirt1 and shirt2 must be different") } if shirt1 == shirt2 { t.Error("shirt1 cannot be equal to shirt2") } t.Logf("LOG - shirt1: %s", shirt1.GetInfo()) t.Logf("LOG - shirt2: %s", shirt2.GetInfo()) } |
コメントを残す
コメントを投稿するにはログインしてください。