![Pyłek](/images/patterns/cards/flyweight-mini.png?id=422ca8d2f90614dce810a8812c626698)
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:
-
Każdy z 10 obiektów graczy tworzy osobny obiekt ubioru i przechowuje go. Stworzone zostanie więc 10 obiektów reprezentujących stroje.
-
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