GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Prototype」を学ぶ"
今回は、Pythonで実装した”Prototype”のサンプルアプリをGolangで実装し直してみました。
■ Prototype(プロトタイプ・パターン)
Prototypeパターン(プロトタイプ・パターン)とは、ソフトウェア開発で用いられる、生成に関するデザインパターンの1つである。生成されるオブジェクトの種別がプロトタイプ(典型)的なインスタンスであるときに使用され、このプロトタイプを複製して新しいオブジェクトを生成する。
このパターンは、以下の目的で用いられる。
- Abstract Factory パターンでなされるように、クライアント・アプリケーションにおいてオブジェクトの生成者をサブクラスにすることを回避する
- 標準的な方法(例えば'new')で新しいオブジェクトを作ることによる固有のコストが所与のアプリケーションにとって高すぎる時にそれを回避する
このパターンを実装するには、純粋仮想 (pure virtual method) の clone()メソッドを指定する抽象的(abstract)な基底クラスを宣言する。「多態性を持つコンストラクタ」の能力を必要とする全てのクラスは抽象的な基底クラスから自身を派生させ、clone()の操作を実装する。
UML class and sequence diagram
UML class diagram
□ 備忘録
Prototype
パターンは、クラスからインスタンスをつくるのではなく、インスタンスをコピーすることで、インスタンスから別のインスタンスをつくるというものです。
Prototype
パターンを活用するメリットについては、書籍「増補改訂版Java言語で学ぶデザインパターン入門」P.66に以下の記述があります。
(1) 種類が多すぎてクラスにまとめられない場合
(2) クラスからインスタンス作成が難しい場合
(3) フレームワークと生成するインスタンスを分けたい場合
まあ、大量に、インスタンスを生成するようなユースケースがあるならば、インスタンス生成に関わるオーバヘッド処理の削減につながるんですかねぇー
■ "Prototype"のサンプルプログラム
実際に、Prototypeパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- 文字列を、下線を引いて表示する
- 文字列を、枠線で囲って表示する
$ go run Main.go
"Hello World"
-------------
***************
* Hello World *
***************
///////////////
/ Hello World /
///////////////
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Prototype
- ディレクトリ構成
.
├── Main.go
└── framework
├── manager.go
├── message_box_prototype.go
├── prototype.go
└── underline_pen_prototype.go
(1) Prototype(原型)の役
Prototype
役は、インスタンスをコピー(複製)して新しいインスタンスを作るためのインタフェースを定めます。
サンプルプログラムでは、Prototype
インタフェースが、この役を努めます。
package prototype
// Prototype is interface
type Prototype interface {
Use(s string)
createClone() Prototype
}
(2) ConcretePrototype(具体的な原型)の役
ConcretePrototype
役は、インスタンスをコピーして新しいインスタンスを作るメソッドを実際に実装します。
サンプルプログラムでは、UnderlinePen
構造体やMessageBox
構造体が、この役を努めます。
package prototype
import (
"fmt"
"strings"
)
// UnderlinePen is struct
type UnderlinePen struct {
ulchar string
}
// NewUnderlinePen func for initializing UnderlinePen
func NewUnderlinePen(ulchar string) *UnderlinePen {
return &UnderlinePen{
ulchar: ulchar,
}
}
// Use func for confirming name
func (u *UnderlinePen) Use(s string) {
length := len(s)
line := strings.Repeat(u.ulchar, (length + 2))
fmt.Printf("\"%s\"\n", s)
fmt.Println(line)
fmt.Println("")
}
func (u *UnderlinePen) createClone() Prototype {
return NewUnderlinePen(u.ulchar)
}
package prototype
import (
"fmt"
"strings"
)
// MessageBox is struct
type MessageBox struct {
decochar string
}
// NewMessageBox func for initializing MessageBox
func NewMessageBox(decochar string) *MessageBox {
return &MessageBox{
decochar: decochar,
}
}
// Use func for confirming name
func (m *MessageBox) Use(s string) {
length := len(s)
line := strings.Repeat(m.decochar, (length + 4))
fmt.Println(line)
fmt.Printf("%s %s %s\n", m.decochar, s, m.decochar)
fmt.Println(line)
fmt.Println("")
}
func (m *MessageBox) createClone() Prototype {
return NewMessageBox(m.decochar)
}
(3) Client(利用者)の役
Client
役は、インスタンスをコピーするメソッドを利用して、新しいインスタンスを作成します。
サンプルプログラムでは、Manager
構造体やstartMain
関数が、この役を努めます。
package prototype
// ManagerInterface is interface
type ManagerInterface interface {
Register(name string, producter Prototype)
Create(protoname string) Prototype
}
// Manager is struct
type Manager struct {
showcase map[string]Prototype
}
// NewManager func for initializing Manager
func NewManager() *Manager {
return &Manager{
showcase: make(map[string]Prototype),
}
}
// Register func for registering proto
func (m *Manager) Register(name string, proto Prototype) {
m.showcase[name] = proto
}
// Create func for creating proto
func (m *Manager) Create(protoname string) Prototype {
p := m.showcase[protoname]
return p.createClone()
}
package main
import (
prototype "./framework"
)
func startMain(managerObject prototype.ManagerInterface) {
upen := prototype.NewUnderlinePen("-")
mbox := prototype.NewMessageBox("*")
sbox := prototype.NewMessageBox("/")
managerObject.Register("strong message", upen)
managerObject.Register("warning box", mbox)
managerObject.Register("slash box", sbox)
p1 := managerObject.Create("strong message")
p2 := managerObject.Create("warning box")
p3 := managerObject.Create("slash box")
p1.Use("Hello World")
p2.Use("Hello World")
p3.Use("Hello World")
}
func main() {
startMain(prototype.NewManager())
}