Привіт! Я знизив ціни на усі продукти. Час разом готувати свої програмерські скіли до пост-COVID-ної ери. Перевірити »
Легковаговик

Легковаговик на Go

Легковаговик — це структурний патерн, який економить пам’ять завдяки розподілу спільного стану, винесеного в один об’єкт, між безліччю об’єктів.

Легковаговик дозволяє економити пам’ять, записуючи в кеш однакові дані, що використовуються різними об’єктами.

Концептуальний приклад

У грі Counter-Strike Терористи і Контртерористи мають різні типи мундира. Для спрощення припустимо, що і Терористи, і Контртерористи мають по одному типу мундира. Об’єкт «мундир» вписаний в об’єкт «гравець» наступним чином:

Нижче наведена структура гравця. Як бачимо, об’єкт «мундир» вписаний в структуру гравця:

type player struct {
    dress      dress
    playerType string // Може бути T або CT
    lat        int
    long       int
}

Припустимо, що у нас є 5 Терористів і 5 Контртерористів, тобто всього 10 гравців. Тоді ми маємо два можливих варіанти створення мундирів:

  1. Кожен з 10 об’єктів гравців створює окремий об’єкт мундира і вбудовує його. Всього створюється 10 об’єктів мундирів.

  2. Ми створюємо 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
На основі: Golang By Example

Легковаговик іншими мовами програмування

Легковаговик на Java Легковаговик на C# Легковаговик на C++ Легковаговик на PHP Легковаговик на Python Легковаговик на Ruby Легковаговик на Swift Легковаговик на TypeScript