
Легковаговик на Go
Легковаговик — це структурний патерн, який економить пам’ять завдяки розподілу спільного стану, винесеного в один об’єкт, між безліччю об’єктів.
Легковаговик дозволяє економити пам’ять, записуючи в кеш однакові дані, що використовуються різними об’єктами.
Концептуальний приклад
У грі Counter-Strike Терористи і Контртерористи мають різні типи мундира. Для спрощення припустимо, що і Терористи, і Контртерористи мають по одному типу мундира. Об’єкт «мундир» вписаний в об’єкт «гравець» наступним чином:
Нижче наведена структура гравця. Як бачимо, об’єкт «мундир» вписаний в структуру гравця:
type player struct {
dress dress
playerType string // Може бути T або CT
lat int
long int
}
Припустимо, що у нас є 5 Терористів і 5 Контртерористів, тобто всього 10 гравців. Тоді ми маємо два можливих варіанти створення мундирів:
-
Кожен з 10 об’єктів гравців створює окремий об’єкт мундира і вбудовує його. Всього створюється 10 об’єктів мундирів.
-
Ми створюємо 2 об’єкти мундирів:
- Єдиний Об’єкт Мундира Терориста — його будуть використовувати 5 Терористів. - Єдиний Об’єкт Мундира Контртерориста – його будуть використовувати 5 Контртерористів.
Як ми бачимо, у Варіанті 1 доведеться створити 10 об’єктів мундирів, тоді як у Варіанті 2 ми створюємо лише 2 об’єкти. Другий підхід — це суть патерна проектування Легковаговик. Два об’єкти мундирів, створені нами, називають легковаговими об’єктами.
Патерн Легковаговик знаходить однакові елементи і створює легковагові об’єкти. Ці легковагові об’єкти (мундири) в подальшому можуть бути поширені між декількома об’єктами (гравці). Така практика значно зменшує кількість об’єктів мундирів, а головне — навіть якщо ми створимо більше гравців, їм однаково буде достатньо лише двох об’єктів мундирів.
Використовуючи патерн Легковаговик, ми зберігаємо легковагові об’єкти в полях карти. Коли створюються інші об’єкти, що розділяють між собою легковагові об’єкти, легковаговики завантажуються з карти.
Тепер давайте подумаємо над тим, які частини цієї системи будуть входити до «внутрішнього» або «зовнішнього стану»:
-
Внутрішній стан: Мундир належить до внутрішнього стану, оскільки він використовується декількома об’єктами Терористів і Контртерористів.
-
Зовнішній стан: Місцезнаходження та зброя гравця належать до зовнішнього стану, оскільки у кожного об’єкта вони різні.
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