REBAJA de primavera
Flyweight

Flyweight en Go

Flyweight es un patrón de diseño estructural que permite a los programas soportar grandes cantidades de objetos manteniendo un bajo uso de memoria.

El patrón lo logra compartiendo partes del estado del objeto entre varios objetos. En otras palabras, el Flyweight ahorra memoria RAM guardando en caché la misma información utilizada por distintos objetos.

Ejemplo conceptual

En un juego de contraataque, el terrorista y el contraterrorista tienen distintos tipos de vestimenta. Por simplificar, asumamos que terrorista y contraterrorista tienen un tipo de vestimenta cada uno. El objeto vestimenta está integrado en el objeto jugador, como se ve a continuación.

A continuación se encuentra la estructura de un jugador. Podemos ver que el objeto vestimenta está integrado en la estructura del jugador:

type player struct {
    dress      dress
    playerType string // Puede ser T o CT
    lat        int
    long       int
}

Digamos que hay 5 terroristas y 5 contraterroristas, de modo que hay un total de 10 jugadores. Hay dos opciones en lo que respecta a la vestimenta.

  1. Cada uno de los 10 objetos jugador crea un objeto vestimenta diferente y lo integra. Se creará un total de 10 objetos de vestimenta.

  2. Creamos dos objetos de vestimenta:

    • Objeto único de vestimenta de terrorista: Lo compartirán 5 terroristas.
    • Objeto único de vestimenta de contraterrorista: Lo compartirán 5 contraterroristas.

Como puedes ver en la solución 1, se crea un total de 10 objetos de vestimenta, mientras que en la solución 2 solo se crean 2 objetos de vestimenta. La segunda solución es la que empleamos en el patrón de diseño Flyweight. Los dos objetos de vestimenta que creamos se denominan objetos flyweight.

El patrón Flyweight extrae las partes comunes y crea objetos flyweight. Estos objetos flyweight (vestimenta) pueden ser compartidos por muchos objetos (jugador). Esto reduce drásticamente el número de objetos de vestimenta y lo bueno es que, aunque crees más jugadores, será suficiente con dos objetos de vestimenta.

En el patrón Flyweight almacenamos los objetos flyweight en el campo mapa. Cuando se crean los otros objetos que comparten los objetos flyweight, los objetos flyweight se extraen del mapa.

Veamos qué partes de este sistema serán estados intrínsecos y extrínsecos:

  • Estado intrínseco: Vestimenta en el estado intrínseco, ya que puede ser compartido por varios objetos de terrorista y contraterrorista.

  • Estado extrínseco: La ubicación y el arma del jugador son un estado extrínseco, ya que son diferentes para cada objeto.

dressFactory.go: Fábrica flyweight

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: Interfaz flyweight

package main

type Dress interface {
	getColor() string
}

terroristDress.go: Objeto concreto flyweight

package main

type TerroristDress struct {
	color string
}

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

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

counterTerroristDress.go: Objeto concreto flyweight

package main

type CounterTerroristDress struct {
	color string
}

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

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

player.go: Contexto

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: Código cliente

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: Resultado de la ejecución

DressColorType: ctDress
DressColor: green
DressColorType: tDress
DressColor: red

Flyweight en otros lenguajes

Flyweight en C# Flyweight en C++ Flyweight en Java Flyweight en PHP Flyweight en Python Flyweight en Ruby Flyweight en Rust Flyweight en Swift Flyweight en TypeScript