[Unity, Design Pattern] 플라이웨이트 패턴

조재훈·2024년 11월 5일
0

플라이웨이트(Flyweight) 패턴

정의

플라이웨이트 패턴은 많은 여러 객체들을 만들 때 모든 객체를 메모리에 유지하는 대신 객체들 간 동일한 부분을 공유하여 메모리 사용을 최소화하는 디자인 패턴이다

객체의 내부 상태를 '변하는 않는 부분'과 '변하는 부분'으로 분리하고 공유 가능한 불변 상태(intrinsic state)을 캐시하여 여러 객체가 공유하고, 변하는 부분(extrinsic state)은 개별 객체마다 별도로 관리하는 방식이다

예를 들어 게임에서 같은 종류의 적이 많이 필요할 때, 개별 적의 위치나 행동처럼 변하는 부분은 각 인스턴스마다 별도로 관리할 수 있다


이를 설명하기 딱 좋은 그림이 있길래 가져와봤다

구조

플라이웨이트 패턴의 주요 구성 요소는

  • Flyweight : 플라이웨이트 인터페이스
    • 플라이웨이트 객체의 인터페이스로, 변하지 않는 속성과 변할 수 있는 속성을 정의한다. 외부로부터 개별 상태를 전달받아 플라이웨이트 객체가 변하지 않는 속성과 함께 사용할 수 있게 한다
  • ConcreteFlyweight : 구체적인 플라이웨이트 클래스
    • Flyweight 인터페이스를 구현한 클래스. 공유할 수 있는 속성을 보유하며, 개별 속성을 사용할 때는 외부에서 전달받아 사용한다
    • 모든 공유 상태는 이 객체에 저장되며, 여러 객체가 이 공유 상태를 참조할 수 있다
  • FlyweightFactory : 플라이웨이트 팩토리
    • 플라이웨이트 객체를 생성하고 관리하는 역할을 한다. 이미 생성된 객체가 있다면 반환하고, 없다면 새로운 객체를 생성해서 반환
    • 객체의 생성과 캐싱을 관리해 메모리 사용을 최소화한다
  • Client
    • 플라이웨이트 객체를 사용하는 주체. 플라이웨이트 객체가 필요할 때 Factory를 통해 인스턴스를 요청하고, 개별 상태를 전달하여 플라이웨이트 객체의 메서드를 호출한다

구현

유니티에서 한 번 플라이웨이트 패턴을 구현해보자

몬스터에는 여러 종류가 있다. 이 종류 안에는 몬스터의 변하지 않는 데이터가 있다(몬스터의 ID, 생김새)

IMonster : 플라이웨이트 인터페이스

몬스터의 기본 동작인 Attack 함수를 정의한 인터페이스다

namespace FlyweightPattern
{
    public interface IMonster
    {
        void Attack(int x, int y);
    }
}

MonsterType : ConcreteFlyweight

몬스터의 종류를 나타내는 클래스

여기선 몬스터의 변하지 않는 공유 상태를 가진다. 추가로 몬스터의 외형을 추가하고 싶으면 변수를 새로 추가하면 된다

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);
    }
}

MonsterSpawner : FlyweightFactory

몬스터 타입을 생성하고 캐싱해 외부로부터 요청이 들어오면 생성하거나 반환하는 클래스

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];
    }
}

몬스터 타입을 새로 만들면 로그를 띄운다

Monster : Client

미리 생성된 몬스터 타입을 통해 몬스터를 만들 수 있다

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인 보스를 불러오는데 이때는 정보를 안들고 있으니까 새로 만들어서 반환한다

결과

마무리

이렇게 플라이웨이트 패턴이라는 것을 간단하게 체험해봤다. 구현해본 결과 은근 유용할 것 같은 패턴이라고 생각이 들었고 이와 비슷한 패턴인 오브젝트 풀링과 상황에 맞게 적절히 쓰면 될 것 같다

profile
나태지옥

0개의 댓글