본문 바로가기

PROGRAMMING/Design Pattern

[GoF] 경량 (Flyweight) 패턴

경량 (Flyweight) 패턴 구조 패턴

중복된 정보를 가진 객체를 공유하여 메모리를 최적화합니다.


경량 (Flyweight) 패턴을 알아보기에 앞서, 이번에는 마인크래프트를 구현해보겠다. 마인크래프트는 수 많은 블럭들로 이루어진 세계를 실체화하는데, 이를 관리하는 기본적인 단위인 청크 (Chunk) 에서 블럭들의 정보가 담겨지게 된다. 블럭을 구현하기 위해 블럭 클래스를 작성하고 청크에서 블럭 클래스를 생성하여 참조하도록 하자. (0, 0, 0) 지점에 블록 객체 생성, 또 (0, 0, 1) 지점에 블럭 객체를 생성하고.. 그렇게 끊임없이 반복하면 한 청크당 16 * 16 * 256 개, 즉 65536 개의 블럭이 생성되어 참조될 것이다.

이는 마인크래프트 세계를 표현해낼 수 있게 하겠지만, 메모리의 입장에선 그리 현명한 방법은 아닐 것이다. 블럭 하나당 100 바이트의 정보만 담더라도 한 청크가 할당하는 용량은 어림잡아 6 메가바이트가 될 것이고, 이걸 수백, 수천 개 관리해야하는 마인크래프트는 세계를 확장해나갈수록 RAM을 잡아먹는 괴물이 될 것이다.

그렇기에 메모리를 줄일 수 있는 방법을 생각해보자. 그러고 보니, 같은 종류의 블럭은 가진 정보도 똑같을 것이다. 차라리 사전에 블럭 객체를 종류마다 하나씩만 생성하고 청크에서 블럭들을 생성하는 대신에 종류에 따라 골라 가져와 참조만 하는것은 어떨까? 직접 구현해보니 결과는 놀라움이었다. 단지 종류만큼의 블럭 객체만 생성했음에도 불구하고 마인크래프트 세계를 멀쩡하게 구현해낼 수 있었다. 실제로 마인크래프트에서는 로드 시에 블럭을 종류마다 레지스트리 객체에 미리 등록하고, 이후 키값을 이용해 가져와 사용하는 방식을 채택하고 있다.

경량 (Flyweight) 패턴은 마인크래프트의 블럭과 같이 중복되는 객체의 생성을 최소한으로 줄이고, 이미 생성된 객체를 가져와 얕은 복사를 수행한다. 그러면, 최소한의 메모리의 할당으로 클라이언트는 여럿이 참조된 객체의 고유 정보에 접근하여 정보를 얻어낼 수 있게 된다. 하지만, 경량화된 객체의 모든 정보는 공유되기에 상태 정보를 넣을 수가 없는데, 이는 상태 정보를 외부에서 가져와 처리할 수 있게 하는 방문자 (Visitor) 패턴을 응용할 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
public class PatternFlyweight
{
    public interface IFlyweight
    {
        void Operation();
    }
 
    public class Flyweight : IFlyweight
    {
        public void Operation()
        {
            Console.WriteLine("Operating Flyweight (" + GetHashCode() + ")");
        }
    }
 
    public class FlyweightFactory
    {
        private Dictionary<string, IFlyweight> _flyweights;
 
        public FlyweightFactory()
        {
            _flyweights = new Dictionary<string, IFlyweight>();
        }
 
        public IFlyweight GetFlyweight(string key)
        {
            IFlyweight flyweight;
 
            _flyweights.TryGetValue(key, out flyweight);
 
            if (flyweight == null)
            {
                flyweight = new Flyweight();
 
                _flyweights.Add(key, flyweight);
            }
 
            return flyweight;
        }
    }
 
    public static void Main(string[] args)
    {
        FlyweightFactory flyweightFactory = new FlyweightFactory();
 
        IFlyweight[] flyweights = new IFlyweight[]
        {
            flyweightFactory.GetFlyweight("A"),
            flyweightFactory.GetFlyweight("A"),
            flyweightFactory.GetFlyweight("B"),
            flyweightFactory.GetFlyweight("C"),
            flyweightFactory.GetFlyweight("C")
        };
 
        foreach (IFlyweight flyweight in flyweights)
        {
            flyweight.Operation();
        }
    }
}
 
cs

1
2
3
4
5
6
Operating Flyweight (46104728)
Operating Flyweight (46104728)
Operating Flyweight (12289376)
Operating Flyweight (43495525)
Operating Flyweight (43495525)
Press any key to continue . . .
cs

위의 예제에서는 키 값을 통해 FlyweightFactory 객체에서 Flyweight 객체를 가져와 참조하는 것을 보여준다. 중복된 키 값을 통해 가져온 Flyweight 객체는 해시(주소) 값이 동일하다.