
Strategy を Go で
Strategy は、 振る舞いに関するデザインパターンの一つで、 一連の振る舞いをオブジェクトに転換し、 元のコンテキスト・オブジェクト内で交換可能とします。
元のオブジェクトは、 コンテキストと呼ばれ、 一つのストラテジー・オブジェクトへの参照を保持し、 それに振る舞いの実行を委任します。 コンテキストがその作業を実行する方法を変えるために、 他のオブジェクトが、 現在リンクされているオブジェクトを違うものと置き換えるかもしれません。
概念的な例
メモリー内キャッシュを構築するとしましょう。 メモリーが相手なので、 サイズは限られています。 最大サイズに達したら、 いくつかの項目を追い出して、 空きスペースを確保する必要があります。 これを行うアルゴリズムにはいろいろあります。 人気のあるものをいくつか羅列すると:
- Least Recently Used (LRU): 使用後最も時間の経った項目を削除。
- First In, First Out (FIFO): 最初に作られた項目を削除。
- Least Frequently Used (LFU): 利用頻度が最低の項目を削除。
実行時にアルゴリズムを変更可能とするために、 キャッシュ・クラスをいかにこれらのアルゴリズムから切り離すかが問題です。 また、 新しいアルゴリズムが追加されても、 キャッシュ・クラスは変更不要であるべきです。
こういう時に、 Strategy パターンが便利です。 アルゴリズムのファミリーを作り、 各アルゴリズムごとに独自のクラスがあるようにします。 これらのクラスは、 同一のインターフェースに従うようにして、 ファミリー内でのアルゴリズムの交換を可能とします。 とりあえず、 共通のインターフェースの名前は、 evictionAlgo
としましょう。
我々の主キャッシュ・クラスは、 evictionAlgo
インターフェースを埋め込んでいます。 あらゆる種類の追い出しアルゴリズムを実装する代わりに、 キャッシュ・クラスは、 それをすべて evictionAlgo
インターフェースに委任します。 evictionAlgo
はインターフェースなので、 アルゴリズムを 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