春のセール
Flyweight

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 人いるとします 服に関しては 二つの扱い方があります

  1. 10 人のプレーヤーのそれぞれが 1 個ずつ異なる服オブジェクトを作成し それを埋め込みます 全部で 10 個の服オブジェクトが作成されます

  2. 服オブジェクトを 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

他言語での Flyweight

Flyweight を C# で Flyweight を C++ で Flyweight を Java で Flyweight を PHP で Flyweight を Python で Flyweight を Ruby で Flyweight を Rust で Flyweight を Swift で Flyweight を TypeScript で