Autumn SALE
Pyłek

Pyłek w języku Go

Pyłek to strukturalny wzorzec projektowy umożliwiający obsługę wielkich ilości obiektów przy jednoczesnej oszczędności pamięci.

Wzorzec Pyłek umożliwia zmniejszenie wymogów w zakresie pamięci RAM poprzez współdzielenie części opisu stanu przez wiele obiektów. Innymi słowy Pyłek przechowuje w pamięci podręcznej te dane, które są wspólne dla wielu różnych obiektów.

Przykład koncepcyjny

W grze Counter-Strike Terroryści i Kontrterroryści różnią się ubiorem. Upraszczając, załóżmy że każda z grup ma po jednym stroju. Informacja o ubiorze jest zawarta w obiekcie gracza, jak pokazano poniżej.

Oto struktura opisująca gracza. Widzimy, że obiekt odpowiadający strojowi jest zagnieżdżony wewnątrz tej struktury:

type player struct {
    dress      dress
    playerType string // Może przyjąć wartość T lub CT
    lat        int
    long       int
}

A teraz załóżmy, że mamy 5 terrorystów i 5 kontrterrorystów, czyli w sumie 10 graczy. W kwestii ubioru są dwie opcje:

  1. Każdy z 10 obiektów graczy tworzy osobny obiekt ubioru i przechowuje go. Stworzone zostanie więc 10 obiektów reprezentujących stroje.

  2. Tworzymy tylko dwa obiekty-stroje:

    • Jeden obiekt-strój terrorysty: Współdzielony przez 5 terrorystów.
    • Jeden obiekt-strój kontrterrorysty: Współdzielony przez 5 kontrterrorystów.

Widzimy więc, że w pierwszym podejściu stworzonych zostanie 10 obiektów reprezentujących ubiór, zaś w drugim jedynie 2. To właśnie drugie podejście jest ideą wzorca projektowego Pyłek. Oba obiekty-stroje, które zostały stworzone, nazwiemy obiektami-pyłkami.

Wzorzec Pyłek zakłada tworzenie obiektów przechowujących wspólne elementy wielu innych obiektów. Tak stworzone pyłki (tu: ubiór) mogą być współdzielone przez wiele obiektów (graczy). Pozwala to drastycznie zmniejszyć liczbę obiektów opisujących stroje, a pojawienie się większej liczby graczy nie skutkuje powieleniem obiektu-stroju.

Zgodnie ze wzorcem Pyłek przechowujemy obiekty-pyłki w polu-mapie. Gdy utworzony zostanie nowy obiekt korzystający z któregoś ze strojów, mapa zwróci pyłek.

Sprawdźmy które elementy tego układu będą stanami wewnętrznymi, a które zewnętrznymi:

  • Stan wewnętrzny: Strój jest częścią stanu wewnętrznego, gdyż może być współdzielony przez wiele obiektów odpowiadających Terrorystom i Kontrterrorystom.

  • Stan zewnętrzny: Lokalizacja gracza i jego broń są częścią stanu zewnętrznego, gdyż są różne dla każdego gracza.

dressFactory.go: Fabryka pyłków

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: Interfejs pyłku

package main

type Dress interface {
	getColor() string
}

terroristDress.go: Konkretny obiekt-pyłek

package main

type TerroristDress struct {
	color string
}

func (t *TerroristDress) getColor() string {
	return t.color
}

func newTerroristDress() *TerroristDress {
	return &TerroristDress{color: "red"}
}

counterTerroristDress.go: Konkretny obiekt-pyłek

package main

type CounterTerroristDress struct {
	color string
}

func (c *CounterTerroristDress) getColor() string {
	return c.color
}

func newCounterTerroristDress() *CounterTerroristDress {
	return &CounterTerroristDress{color: "green"}
}

player.go: Kontekst

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: Kod klienta

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: Kod klienta

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: Wynik działania

DressColorType: ctDress
DressColor: green
DressColorType: tDress
DressColor: red
Na podstawie: Golang By Example

Pyłek w innych językach

Pyłek w języku C# Pyłek w języku C++ Pyłek w języku Java Pyłek w języku PHP Pyłek w języku Python Pyłek w języku Ruby Pyłek w języku Rust Pyłek w języku Swift Pyłek w języku TypeScript