GoFのデザインパターンを学習する素材として、書籍「増補改訂版Java言語で学ぶデザインパターン入門」が参考になるみたいですね。
取り上げられている実例は、JAVAベースのため、Pythonで同等のプラクティスに挑んだことがありました。
Qiita記事: "Pythonで、デザインパターン「Flyweight」を学ぶ"
今回は、Pythonで実装した”Flyweight”のサンプルアプリをGolangで実装し直してみました。
■ Flyweight(フライウェイト・パターン)
Flyweightパターン(フライウェイト・パターン)とは、GoFによって定義されたデザインパターンの1つである。 等価なインスタンスを別々の箇所で使用する際に、一つのインスタンスを再利用することによってプログラムを省リソース化することを目的とする。
UML class and sequence diagram
UML class diagram
□ 備忘録
flyweightというのは、「フライ級」のことで、ボクシングで最も体重が軽い階級を示すものであり、このデザインパターンでは、オブジェクトを「軽く」するためのものだそうです。
Flyweight
パターンで使っている技法は、「インスタンスをできるだけ共有させて、無駄にnewしない」というもので、インスタンスが必要なときに、いつもnewするのではなく、すでに作ってあるインスタンスを利用できるなら、それを共有して使うものだそうです。
直感的に、デザインパターン「Singleton」の応用だと感じますね。
■ "Flyweight"のサンプルプログラム
##(1) 事前準備
アクキーアートっぽく、数字を表示するテキストファイルを準備しておきます。
....######......
..##......##....
..##......##....
..##......##....
..##......##....
..##......##....
....######......
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
(以下、略)
(2) 動作確認
実際に、Flyweightパターンを活用したサンプルプログラムを動かしてみて、次のような動作の様子を確認したいと思います。
- 引数の数字の順番に、指定された数字をアスキーアートで表示する
$ go run Main.go 012123
....######......
..##......##....
..##......##....
..##......##....
..##......##....
..##......##....
....######......
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
......##........
..######........
......##........
......##........
......##........
......##........
..##########....
................
....######......
..##......##....
..........##....
......####......
....##..........
..##............
..##########....
................
....######......
..##......##....
..........##....
......####......
..........##....
..##......##....
....######......
................
■ サンプルプログラムの詳細
Gitリポジトリにも、同様のコードをアップしています。
https://github.com/ttsubo/study_of_design_pattern_with_golang/tree/master/Flyweight
- ディレクトリ構成
.
├── Main.go
├── big0.txt
├── big1.txt
├── big2.txt
├── big3.txt
├── big4.txt
├── big5.txt
├── big6.txt
├── big7.txt
├── big8.txt
├── big9.txt
└── flyweight
└── big_char_factory.go
(1) Flyweight(フライ級)の役
普通に扱うとプログラムが重くなるので共有した方が良いものを表す役です。
サンプルプログラムでは、bigChar
構造体が、この役を努めます。
package flyweight
import (
"fmt"
"os"
)
... (snip)
type bigChar struct {
fontdata string
}
func newBigChar(charname string) *bigChar {
char := &bigChar{}
data := make([]byte, 256)
f, err := os.Open(fmt.Sprintf("big%s.txt", charname))
if err == nil {
f.Read(data)
char.fontdata = string(data)
} else {
char.fontdata = charname + "?"
}
return char
}
func (b *bigChar) print() {
fmt.Println(b.fontdata)
}
(2) FlyweightFactory(フライ級の工場)の役
Flyweight
を作る工場の役です。この工場を使ってFlyweight
役を作ると、インスタンスが共有されます。
サンプルプログラムでは、bigCharFactory
構造体が、この役を努めます。
type bigCharFactory struct {
pool map[string]*bigChar
}
func newBigCharFactory() *bigCharFactory {
bigChrFct := &bigCharFactory{
pool: make(map[string]*bigChar),
}
return bigChrFct
}
var instance *bigCharFactory
func getInstance() *bigCharFactory {
if instance == nil {
instance = newBigCharFactory()
}
return instance
}
func (b *bigCharFactory) getBigChar(charname string) *bigChar {
var bc *bigChar
if _, ok := b.pool[charname]; !ok {
bc = newBigChar(charname)
b.pool[charname] = bc
} else {
bc = b.pool[charname]
}
return bc
}
(3) Client(依頼人)の役
FlyweightFactory
役を使ってFlyweight
を作り出し、それを利用する役です。
サンプルプログラムでは、BigString
構造体とstartMain
関数が、この役を努めます。
// BigString is struct
type BigString struct {
bigchars []*bigChar
}
// NewBigString func for initalizing BigString
func NewBigString(str string) *BigString {
bigStr := &BigString{}
factory := getInstance()
for _, s := range str {
bigStr.bigchars = append(bigStr.bigchars, factory.getBigChar(string(s)))
}
return bigStr
}
// Print func for print something
func (b *BigString) Print() {
for _, bc := range b.bigchars {
bc.print()
}
}
package main
import (
"flag"
"./flyweight"
)
func startMain(str string) {
bs := flyweight.NewBigString(str)
bs.Print()
}
func main() {
flag.Parse()
startMain(flag.Arg(0))
}