春のセール
Strategy

Strategy を Go で

Strategy 振る舞いに関するデザインパターンの一つで 一連の振る舞いをオブジェクトに転換し 元のコンテキスト・オブジェクト内で交換可能とします

元のオブジェクトは コンテキストと呼ばれ 一つのストラテジー・オブジェクトへの参照を保持し それに振る舞いの実行を委任します コンテキストがその作業を実行する方法を変えるために 他のオブジェクトが 現在リンクされているオブジェクトを違うものと置き換えるかもしれません

概念的な例

メモリー内キャッシュを構築するとしましょう メモリーが相手なので サイズは限られています 最大サイズに達したら いくつかの項目を追い出して 空きスペースを確保する必要があります これを行うアルゴリズムにはいろいろあります 人気のあるものをいくつか羅列すると

  • Least Recently Used (LRU) 使用後最も時間の経った項目を削除
  • First In, First Out (FIFO) 最初に作られた項目を削除
  • Least Frequently Used (LFU) 利用頻度が最低の項目を削除

実行時にアルゴリズムを変更可能とするために キャッシュ・クラスをいかにこれらのアルゴリズムから切り離すかが問題です また 新しいアルゴリズムが追加されても キャッシュ・クラスは変更不要であるべきです

こういう時に Strategy パターンが便利です アルゴリズムのファミリーを作り 各アルゴリズムごとに独自のクラスがあるようにします これらのクラスは 同一のインターフェースに従うようにして ファミリー内でのアルゴリズムの交換を可能とします とりあえず 共通のインターフェースの名前は eviction­Algo としましょう

我々の主キャッシュ・クラスは eviction­Algo インターフェースを埋め込んでいます あらゆる種類の追い出しアルゴリズムを実装する代わりに キャッシュ・クラスは それをすべて eviction­Algo インターフェースに委任します eviction­Algo はインターフェースなので アルゴリズムを LRU FIFO LFU のどれにでも キャッシュ・クラスの変更なしに 実行時変更できます

evictionAlgo.go: ストラテジー・インターフェース

package main

type EvictionAlgo interface {
	evict(c *Cache)
}

fifo.go: 具象ストラテジー

package main

import "fmt"

type Fifo struct {
}

func (l *Fifo) evict(c *Cache) {
	fmt.Println("Evicting by fifo strtegy")
}

lru.go: 具象ストラテジー

package main

import "fmt"

type Lru struct {
}

func (l *Lru) evict(c *Cache) {
	fmt.Println("Evicting by lru strtegy")
}

lfu.go: 具象ストラテジー

package main

import "fmt"

type Lfu struct {
}

func (l *Lfu) evict(c *Cache) {
	fmt.Println("Evicting by lfu strtegy")
}

cache.go: コンテキスト

package main

type Cache struct {
	storage      map[string]string
	evictionAlgo EvictionAlgo
	capacity     int
	maxCapacity  int
}

func initCache(e EvictionAlgo) *Cache {
	storage := make(map[string]string)
	return &Cache{
		storage:      storage,
		evictionAlgo: e,
		capacity:     0,
		maxCapacity:  2,
	}
}

func (c *Cache) setEvictionAlgo(e EvictionAlgo) {
	c.evictionAlgo = e
}

func (c *Cache) add(key, value string) {
	if c.capacity == c.maxCapacity {
		c.evict()
	}
	c.capacity++
	c.storage[key] = value
}

func (c *Cache) get(key string) {
	delete(c.storage, key)
}

func (c *Cache) evict() {
	c.evictionAlgo.evict(c)
	c.capacity--
}

main.go: クライアント・コード

package main

func main() {
	lfu := &Lfu{}
	cache := initCache(lfu)

	cache.add("a", "1")
	cache.add("b", "2")

	cache.add("c", "3")

	lru := &Lru{}
	cache.setEvictionAlgo(lru)

	cache.add("d", "4")

	fifo := &Fifo{}
	cache.setEvictionAlgo(fifo)

	cache.add("e", "5")

}

output.txt: 実行結果

Evicting by lfu strtegy
Evicting by lru strtegy
Evicting by fifo strtegy

他言語での Strategy

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