![Flyweight](/images/patterns/cards/flyweight-mini.png?id=422ca8d2f90614dce810a8812c626698)
Flyweight を Go で
Flyweight は、 構造に関するデザインパターンの一つで、 メモリー消費量を低く抑えることで、 プログラムが膨大な数のオブジェクトを支えることができるようにします。
複数のオブジェクト間でオブジェクトの状態の一部を共有することにより、 これを実現します。 つまり、 Flyweight は、 異なるオブジェクトによって使われる同じデータをキャッシュすることにより、 RAM を節約します。
概念的な例
Counter-Strike (反撃) というゲームでは、 テロリストと反テロリストは、 それぞれ違う種類の服を着ています。 話を簡単にするため、 テロリストと反テロリストには、 それぞれ 1 種類の服しかないとしましょう。 以下に示すように、 服オブジェクトは、 プレーヤー・オブジェクトに埋め込まれています。
プレーヤーの構造体は、 以下の通りです。 服 (dress) オブジェクトは、 プレーヤーの構造体に埋め込まれています:
type player struct {
dress dress
playerType string // T(テロリスト) か CT(反テロリスト)
lat int
long int
}
テロリストが 5 人、 反テロリストが 5 人、 全員でプレーヤーが 10 人いるとします。 服に関しては、 二つの扱い方があります。
-
10 人のプレーヤーのそれぞれが 1 個ずつ異なる服オブジェクトを作成し、 それを埋め込みます。 全部で 10 個の服オブジェクトが作成されます。
-
服オブジェクトを 2 個作成:
- テロリストの服オブジェクト一つ: これを、 テロリスト 5 人で共有します。
- 反テロリストの服オブジェクト一つ: これを、 反テロリスト 5 人で共有します。
一つ目の方法では、 全部で 10 個の服オブジェクトが作成されるのに対し、 二つ目の方法では、 たった 2 個の服オブジェクトが作成されます。 二つ目の方法が、 Flyweight デザインパターンです。 作成された 2 個の服オブジェクトのことを、 フライウェイト・オブジェクトと呼びます。
Flyweight パターンでは、 共通の部分を取り出し、 フライウェイト・オブジェクトを作成します。 これらのフライウェイト・オブジェクト (服) は、 複数のオブジェクト (プレーヤー) で共有します。 これは、 ドレス・オブジェクトの数を大幅に削減します。 この利点は、 もしもっと多数のプレーヤーを作成したとしても、 たった 2 個の服オブジェクトだけで十分だということです。
Flyweight パターンでは、 フライウェイト・オブジェクトは、 マップ・フィールドに保存します。 フライウェイト・オブジェクトを共有するオブジェクトが作成されると、 フライウェイト・オブジェクトはマップから取得します。
このお膳立てのどの部分が内因的状態で、 どれが外因的状態かを見てみましょう:
-
内因的状態: 服は複数のテロリスト・オブジェクトと反テトリスト・オブジェクトで共有されるため、 内因的状態。
-
外因的状態: プレーヤーの位置と武器は、 オブジェクトごとに異なるので、 外因的状態。
dressFactory.go: フライウェイト・ファクトリー
package main
import "fmt"
const (
//TerroristDressType terrorist dress type
TerroristDressType = "tDress"
//CounterTerrroristDressType terrorist dress type
CounterTerrroristDressType = "ctDress"
)
var (
dressFactorySingleInstance = &DressFactory{
dressMap: make(map[string]Dress),
}
)
type DressFactory struct {
dressMap map[string]Dress
}
func (d *DressFactory) getDressByType(dressType string) (Dress, error) {
if d.dressMap[dressType] != nil {
return d.dressMap[dressType], nil
}
if dressType == TerroristDressType {
d.dressMap[dressType] = newTerroristDress()
return d.dressMap[dressType], nil
}
if dressType == CounterTerrroristDressType {
d.dressMap[dressType] = newCounterTerroristDress()
return d.dressMap[dressType], nil
}
return nil, fmt.Errorf("Wrong dress type passed")
}
func getDressFactorySingleInstance() *DressFactory {
return dressFactorySingleInstance
}
dress.go: フライウェイト・インターフェース
package main
type Dress interface {
getColor() string
}
terroristDress.go: 具象フライウェイト・オブジェクト
package main
type TerroristDress struct {
color string
}
func (t *TerroristDress) getColor() string {
return t.color
}
func newTerroristDress() *TerroristDress {
return &TerroristDress{color: "red"}
}
counterTerroristDress.go: 具象フライウェイト・オブジェクト
package main
type CounterTerroristDress struct {
color string
}
func (c *CounterTerroristDress) getColor() string {
return c.color
}
func newCounterTerroristDress() *CounterTerroristDress {
return &CounterTerroristDress{color: "green"}
}
player.go: コンテキスト
package main
type Player struct {
dress Dress
playerType string
lat int
long int
}
func newPlayer(playerType, dressType string) *Player {
dress, _ := getDressFactorySingleInstance().getDressByType(dressType)
return &Player{
playerType: playerType,
dress: dress,
}
}
func (p *Player) newLocation(lat, long int) {
p.lat = lat
p.long = long
}
game.go: クライアント・コード
package main
type game struct {
terrorists []*Player
counterTerrorists []*Player
}
func newGame() *game {
return &game{
terrorists: make([]*Player, 1),
counterTerrorists: make([]*Player, 1),
}
}
func (c *game) addTerrorist(dressType string) {
player := newPlayer("T", dressType)
c.terrorists = append(c.terrorists, player)
return
}
func (c *game) addCounterTerrorist(dressType string) {
player := newPlayer("CT", dressType)
c.counterTerrorists = append(c.counterTerrorists, player)
return
}
main.go: クライアント・コード
package main
import "fmt"
func main() {
game := newGame()
//Add Terrorist
game.addTerrorist(TerroristDressType)
game.addTerrorist(TerroristDressType)
game.addTerrorist(TerroristDressType)
game.addTerrorist(TerroristDressType)
//Add CounterTerrorist
game.addCounterTerrorist(CounterTerrroristDressType)
game.addCounterTerrorist(CounterTerrroristDressType)
game.addCounterTerrorist(CounterTerrroristDressType)
dressFactoryInstance := getDressFactorySingleInstance()
for dressType, dress := range dressFactoryInstance.dressMap {
fmt.Printf("DressColorType: %s\nDressColor: %s\n", dressType, dress.getColor())
}
}
output.txt: 実行結果
DressColorType: ctDress
DressColor: green
DressColorType: tDress
DressColor: red