
Стратегія на Go
Стратегія — це поведінковий патерн, який виносить набір алгоритмів у власні класи і робить їх взаємозамінними.
Інші об’єкти містять посилання на об’єкт-стратегію та делегують їй роботу. Програма може підмінити цей об’єкт іншим, якщо потрібен інший спосіб вирішення завдання.
Концептуальний приклад
Уявіть, що ви розробляєте «In-Memory-Cache». Оскільки він розташований всередині пам’яті, його розмір обмежений. Щойно він вщент заповниться, якісь записи доведеться прибрати, аби звільнити простір. Цю функцію можна реалізувати завдяки декільком алгоритмам, найпопулярніші серед них:
- Найбільш давно використовувалися (Least Recently Used — LRU): прибирає запис, який використовувався найбільш давно.
- «Першим прийшов, першим пішов» (First In, First Out — FIFO): прибирає запис, який був створений раніше за інші.
- Найменш часто використовувалися (Least Frequently Used — LFU): прибирає запис, який використовувався найменш часто.
Проблема полягає в тому, щоб відокремити кеш від цих алгоритмів для можливості їх заміни «на ходу». Крім цього, клас кешу мусить не змінюватися при додаванні нового алгоритму.
У такій ситуації нам допоможе патерн Стратегія. Він передбачає створення сімейства алгоритмів, кожен з яких має свій клас. Всі класи застосовують однаковий інтерфейс, що робить алгоритми взаємозамінними всередині цього сімейства. Назвемо цей загальний інтерфейс 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