Prototypeパターンとは
Prototypeパターンは、既存のオブジェクトのコピーをそのクラス(構造体)に依存することなく可能にする、生成に関するデザインパターン。
例えば下記のようにして既存オブジェクトのフィールドを利用して、新しくオブジェクトを作成することは可能である。しかしこの場合、コピーをしようとする側はコピー元のオブジェクトの実装について知っている必要があり依存関係ができてしまう。また構造体が別パッケージに定義されている場合は非公開オブジェクトにアクセスできないため、既存オブジェクトのフィールドを利用できない。
type Item struct {
Id int
name string
}
func main() {
i := Item{Id: 1, name: "test"}
copy := Item{Id: i.Id, name: i.name}
fmt.Println(i)
fmt.Println(copy)
}
// 実行結果
// >> go run .
// {1 test}
// {1 test}
上記のような問題を解決するのがPrototypeパターンになる。このパターンではオブジェクトの複製を行う責任は複製されるオブジェクト自身に課される。
Prototypeパターンを利用することで下記のようなメリットが得られる。
- 具象クラス(構造体)に依存することなく、オブジェクトの複製を行うことができる。
- 複雑なオブジェクトの構成の生成が簡単に行える。
クラス図
PrototypeRegistryはよく使われるプロトタイプに簡単にアクセスできるようにするために実装される。
必要ない場合は実装せず、PrototypeのClone()を直接呼びだしてもよい。

実装
framworkパッケージ
Prototype
package framework
type Product interface {
Use(s string)
CreateClone() Product // 自身のオブジェクトを複製する関数
}
PrototypeRegistry
package framework
import (
"fmt"
)
// Prototypeオブジェクトを保持するオブジェクト
// 名称とオブジェクトを紐づけて保持する
type Registry struct {
showcase map[string]Product
}
func NewRegistry() *Registry {
return &Registry{showcase: map[string]Product{}}
}
func (m *Registry) Register(name string, proto Product) {
m.showcase[name] = proto
}
// 要求されたオブジェクトの複製を実行する。
// 具体的な構造体に依存せず、Productインターフェースのみに依存している
func (m *Registry) Create(protoName string) (*Product, error) {
p, ok := m.showcase[protoName]
if !ok {
return nil, fmt.Errorf("not registerd")
}
c := p.CreateClone()
return &c, nil
}
mainパッケージ
ConcretePrototype
package main
import (
. "prototype/framework"
)
type MessageBox struct {
meessages []string
}
func NewMessageBox() *MessageBox {
return &MessageBox{meessages: []string{}}
}
func (m *MessageBox) Use(s string) {
m.meessages = append(m.meessages, s)
}
// 自身の複製を行う関数
func (m *MessageBox) CreateClone() Product {
return &MessageBox{meessages: m.meessages[:]}
}
動作確認
package main
import (
"fmt"
. "prototype/framework"
)
func main() {
var pr Registry = *NewRegistry()
var mb MessageBox = *NewMessageBox()
mb.Use("message 1")
mb.Use("message 2")
mb.Use("message 3")
const name string = "messageBox"
pr.Register(name, &mb)
copy, _ := pr.Create(name)
c := *(*copy).(*MessageBox)
fmt.Printf("Original address: %p value: %v\n", &mb, mb)
fmt.Printf("Copy address: %p value: %v\n", &c, c)
}
>> go run .
Original address: 0xc000008078 value: {[message 1 message 2 message 3]}
Copy address: 0xc0000080a8 value: {[message 1 message 2 message 3]}
別アドレスで同じ値を持ったインスタンスが生成できていることが確認できる。
コメント