Привет! Я понизил цены на все продукты. Пора готовить свои программерские скилы к пост-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