Goでデザインパターン (Flyweightパターン)

Goデザインパターン

Flyweightパターンとは

構造に関するデザインパターンの一つ。複数のオブジェクトに共通の部分を分離し、共通部分オブジェクトを共有することによってメモリ使用量を最適化することができる。
オブジェクトが保持する状態のうち、共有される状態のことを内因的状態(Intrinsic State)、各オブジェクト毎に持つ状態のことを外因的状態(Extrinsic State)と呼ぶ。

長所

  • 類似オブジェクトの共通部分を共有することによってメモリ使用量と大幅に削減できる可能性がある。
  • 重たいオブジェクトを共有するため、新規オブジェクトの生成時間を削減できる。

短所

  • Factoryで管理されているオブジェクトはガベージコレクションされない。そのため明示的にFactoryのプールから参照を削除する必要がある。
  • コードが煩雑化する。

利用場面

  • 使用可能なRAMに収まらないほど大量のオブジェクトの生成が必要となる場合。
    (コードが煩雑化するため、通常はメモリ上の問題が予想・発生する場合に採用するべき)

クラス図

Flyweightパターン

Flyweight

元のオブジェクトの状態の内、複数のオブジェクト間で共有できる状態を保持する。
Flyweightで保持する状態のことを内因的状態(Intrinsic State)と呼ぶ。
複数オブジェクトで共有されるため、通常Flyweightのフィールドは変更できないように実装する必要がある。
通常は元オブジェクトでの振る舞いはFlyweightクラスに残ることになる。この場合、メソッド実行時には外因的状態を受け取る場合もある。ただし、Contextに振る舞いを実装することもできる。

Context

元オブジェクトの状態の内、各オブジェクト特有の状態を保持する。
Contextで保持する状態のことを外因的状態(Extrinsic State)と呼ぶ。
またFlyweightオブジェクトへの参照を保持することにより、元のオブジェクトの状態を表現する。

Flyweight Factory

Flyweightオブジェクトの生成を行い、既存Flyweightオブジェクトの管理を行う。
クライアントはFlyweight FactoryにFlyweightオブジェクトの生成を依頼する。Factoryはプールに依頼されたFlyweightオブジェクトが存在するかを確認し、存在する場合はそのオブジェクトを返し、しない場合はオブジェクトの新規生成を行う。

実装例

Flyweight

package main

import (
	"fmt"
)

// Flyweightクラス
// それぞれのオブジェクトに共通なフィールド(内因的状態 intrinsic state)を保持する
// 今回はrune文字のみだが、通常はメモリ消費が大きいものがFlyweightになる
type Char struct {
	char rune
}

// 初期化時に共通なフィールド(内因的状態 intrinsic state)を渡す。
func NewChar(char rune) *Char {
	fmt.Printf("Create character instance. [char: %s]\n", string(char))
	return &Char{char: char}
}

func (c *Char) Print() {
	fmt.Print(string(c.char))
}

Context

package main

import (
	"fmt"
)

// Contextクラス
// 外因的状態(extrinsic state)を保持する
// オブジェクトの数だけ生成されることになるが、メモリ消費量が大きい部分は全てFlyweightクラスに分離されているため通常は問題にならない
type Sentence struct {
	charList []*Char
}

func NewSentence(runeList []rune) *Sentence {
	charList := []*Char{}
	factory := GetCharFactory()
	for _, r := range runeList {
		char := factory.GetChar(r)
		charList = append(charList, char)
	}
	return &Sentence{charList: charList}
}

func (s *Sentence) Print(decorator string) {
	fmt.Print(decorator)
	for _, c := range s.charList {
		c.Print()
	}
	fmt.Println(decorator)
}

Flyweight Factory

package main

import "sync"

var instance CharFactory
var mu sync.Mutex

func init() {
	instance = CharFactory{pool: map[rune]*Char{}}
}

// Flyweightを生成・プールするファクトリ
type CharFactory struct {
	pool map[rune]*Char
}

func GetCharFactory() *CharFactory {
	return &instance
}

// Flyweight生成時に内因的状態を指定する
// プールを確認し、存在しなければ新規生成、既存ならばそのオブジェクトを返す
func (f *CharFactory) GetChar(char rune) *Char {
	// ほぼ同時にこのメソッドが呼ばれる場合を考えて排他制御をおこなう
	defer mu.Unlock()
	mu.Lock()
	if v, ok := f.pool[char]; ok {
		return v
	}

	c := NewChar(char)
	f.pool[char] = c
	return c
}

動作確認

package main

import "fmt"

func main() {
	s1 := NewSentence([]rune{'し', 'ん', 'ぶ', 'ん', 'し'})
	s1.Print("==")

	s2 := NewSentence([]rune{'し', 'し', 'お', 'ど', 'し'})
	s2.Print("~~")

	fmt.Printf("pool size = %d", len(GetCharFactory().pool))
}
>> go run .
Create character instance. [char: し]
Create character instance. [char: ん]
Create character instance. [char: ぶ]
==しんぶんし==
Create character instance. [char: お]
Create character instance. [char: ど]
~~ししおどし~~
pool size = 5

コメント

タイトルとURLをコピーしました