플라이웨이트 패턴은 많은 여러 객체들을 만들 때 모든 객체를 메모리에 유지하는 대신 객체들 간 동일한 부분을 공유하여 메모리 사용을 최소화하는 디자인 패턴이다
객체의 내부 상태를 '변하는 않는 부분'과 '변하는 부분'으로 분리하고 공유 가능한 불변 상태(intrinsic state)을 캐시하여 여러 객체가 공유하고, 변하는 부분(extrinsic state)은 개별 객체마다 별도로 관리하는 방식이다
예를 들어 게임에서 같은 종류의 적이 많이 필요할 때, 개별 적의 위치나 행동처럼 변하는 부분은 각 인스턴스마다 별도로 관리할 수 있다
이를 설명하기 딱 좋은 그림이 있길래 가져와봤다
플라이웨이트 패턴의 주요 구성 요소는
Flyweight
인터페이스를 구현한 클래스. 공유할 수 있는 속성을 보유하며, 개별 속성을 사용할 때는 외부에서 전달받아 사용한다Factory
를 통해 인스턴스를 요청하고, 개별 상태를 전달하여 플라이웨이트 객체의 메서드를 호출한다유니티에서 한 번 플라이웨이트 패턴을 구현해보자
몬스터에는 여러 종류가 있다. 이 종류 안에는 몬스터의 변하지 않는 데이터가 있다(몬스터의 ID, 생김새)
몬스터의 기본 동작인 Attack
함수를 정의한 인터페이스다
namespace FlyweightPattern
{
public interface IMonster
{
void Attack(int x, int y);
}
}
몬스터의 종류를 나타내는 클래스
여기선 몬스터의 변하지 않는 공유 상태를 가진다. 추가로 몬스터의 외형을 추가하고 싶으면 변수를 새로 추가하면 된다
public class MonsterType : IMonster
{
private int ID;
private string type;
public MonsterType(int ID, string type)
{
this.ID = ID;
this.type = type;
}
public void Attack(int x, int y)
{
Debug.Log("MonsterType: " + type + " is attacking from " + x + ", " + y);
}
}
몬스터 타입을 생성하고 캐싱해 외부로부터 요청이 들어오면 생성하거나 반환하는 클래스
using System.Collections.Generic;
using UnityEngine;
public class MonsterSpawner
{
private Dictionary<int, MonsterType> monsterTypes = new Dictionary<int, MonsterType>();
public MonsterType GetMonsterType(int ID, string type)
{
if (monsterTypes.ContainsKey(ID) == false)
{
Debug.Log("MonsterType: " + type + " is created");
monsterTypes[ID] = new MonsterType(ID, type);
}
return monsterTypes[ID];
}
}
몬스터 타입을 새로 만들면 로그를 띄운다
미리 생성된 몬스터 타입을 통해 몬스터를 만들 수 있다
public class Monster
{
private int x;
private int y;
private MonsterType monsterType;
public Monster(int x, int y, MonsterType monsterType)
{
this.x = x;
this.y = y;
this.monsterType = monsterType;
}
public void Attack()
{
monsterType.Attack(x, y);
}
}
외부로부터 몬스터의 위치를 받는다
이제 메인 로직에서 몬스터를 소환해보자
using UnityEngine;
namespace FlyweightPattern
{
public class Game : MonoBehaviour
{
MonsterSpawner monsterSpawner = new MonsterSpawner();
void Awake()
{
for (int i = 0; i < 10; i++)
{
monsterSpawner.GetMonsterType(i, $"Monster {i}");
}
}
void Start()
{
for (int i = 0; i < 10; i++)
{
Monster monster = new Monster(Random.Range(0, 10), Random.Range(0, 10), monsterSpawner.GetMonsterType(i, $"Monster {i}"));
monster.Attack();
}
Monster newMonster = new Monster(10, 10, monsterSpawner.GetMonsterType(10, $"Boss"));
newMonster.Attack();
}
}
}
Game 인스턴스가 생성될 때 10개의 몬스터 타입을 미리 생성한다
그리고 몬스터를 생성하는데 이때는 이미 있는 몬스터 타입을 받아와 위치 값만 랜덤으로 생성하고 공격한다
그리고 새로 ID가 10인 보스를 불러오는데 이때는 정보를 안들고 있으니까 새로 만들어서 반환한다
이렇게 플라이웨이트 패턴이라는 것을 간단하게 체험해봤다. 구현해본 결과 은근 유용할 것 같은 패턴이라고 생각이 들었고 이와 비슷한 패턴인 오브젝트 풀링과 상황에 맞게 적절히 쓰면 될 것 같다