겨울 세일!
플라이웨이트

Go로 작성된 플라이웨이트

플라이웨이트는 구조 패턴이며 프로그램들이 객체들의 메모리 소비를 낮게 유지하여 방대한 양의 객체들을 지원할 수 있도록 합니다.

이 패턴은 여러 객체 사이의 객체 상태를 공유하여 위를 달성합니다. 다르게 설명하자면 플라이웨이트는 다른 객체들이 공통으로 사용하는 데이터를 캐싱하여 RAM을 절약합니다.

개념적인 예시

카운터-스트라이크 게임에서 테러리스트와 대테러 요원은 각각 여러 유형의 복장​(dress)​을 가지고 있으나 예시의 단순화를 위해 테러리스트와 대테러 요원 모두 각각 하나의 드레스 유형을 가지고 있다고 가정해 보겠습니다. 드레스 객체는 아래와 같이 플레이어 객체에 내장되어 있습니다.

아래는 플레이어를 위한 구조체​(struct)​이며 드레스 객체가 플레이어 구조체에 포함되어 있음을 알 수 있습니다:

type player struct {
    dress      dress
    playerType string // Can be T or CT
    lat        int
    long       int
}

5명의 테러리스트와 5명의 대테러 요원들, 그러니까 총 10명의 플레이어가 있다고 가정해 봅시다. 이제 복장과 관련하여 두 가지 옵션이 있습니다.

  1. 10명의 플레이어 객체들은 각각 다른 복장 객체를 만든 후 포함시킵니다. 총 10개의 복장 객체들이 생성됩니다.

  2. 우리는 두 개의 복장 객체를 만듭니다:

    • 단일 테러리스트 복장 객체: 5명의 테러리스트가 공유합니다.
    • 단일 대테러 요원 복장 객체: 5명의 대테러 요원들이 공유합니다.

첫 번째 접근법에서는 총 10개의 복장 객체들이 생성되나 두 번째 접근법에서는 두 개의 복장 객체만 생성됩니다. 플라이웨이트 패턴은 두 번째 접근법을 따릅니다. 우리가 만든 두 개의 복장 객체들을 플라이웨이트 객체라고 합니다.

플라이웨이트 패턴은 공통부분들을 제거하고 플라이웨이트 객체들을 생성합니다. 플라이웨이트 객체들​(복장)​은 이제 여러 객체​(플레이어) 간에 공유될 수 있습니다. 이렇게 하면 복장 객체들의 수가 크게 줄어들며, 플레이어들을 더 많이 생성하더라도 두 개의 복장 객체로 충분하다는 점입니다.

플라이웨이트 패턴에서 우리는 플라이웨이트 객체들을 맵 필드에 저장합니다. 플라이웨이트 객체들을 공유하는 다른 객체들이 생성될 때마다 플라이웨이트 객체들은 맵에서 가져와집니다.

이 구조의 어떤 부분이 고유한 상태와 공유한 상태가 되는지 봅시다.

  • : 여러 테러리스트 및 대테러 요원 객체에서 공유할 수 있으므로 복장은 고유한 상태입니다.

  • : 플레이어 위치와 플레이어 무기는 객체마다 다르므로 공유한 상태입니다.

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

다른 언어로 작성된 플라이웨이트

C#으로 작성된 플라이웨이트 C++로 작성된 플라이웨이트 자바로 작성된 플라이웨이트 PHP로 작성된 플라이웨이트 파이썬으로 작성된 플라이웨이트 루비로 작성된 플라이웨이트 러스트로 작성된 플라이웨이트 스위프트로 작성된 플라이웨이트 타입스크립트로 작성된 플라이웨이트