디자인 패턴/GoF

[디자인 패턴] 구조 패턴 (6) 플라이급 Flyweight

로파이 2021. 1. 29. 18:03

플라이급(Flyweight) 패턴

 

1. 의도

  • 표현 상태가 적은 객체들을 공유함으로 효율적으로 시스템을 지원

2. 활용

  • 응용프로그램이 대량의 객체를 사용할 때
  • 객체 수가 많아져서 전체가 사용하는 메모리 용량이 클 때
  • 객체를 구성하는 상태가 적을 떄

예시) 문서 편집기의 문자를 표현하는 방법 

 

문서 편집기의 내용을 표시하기 위애 여러 그래픽 요소를 객체로 관리하며 이는 효과적인 표시를 가능하게 한다.

만약 문서 내용을 나타내는 문자 하나하나를 객체로 관리한다면, 굉장히 많은 객체를 문서마다 보유하고 이는 메모리 문제와 그래픽을 그리는 실행시간이 크게 지연될 것이다.

 

플라이급 객체공유 가능한 객체들을 의미하고 본질적(intrinsic) 상태와 부가적(extrinsic) 상태로 구분된다. 본질적 상태는 플라이급 객체에 저장되어야 하고 부가적 상태는 외부 환경 요소로 상황마다 플라이급 객체를 다른 형태로 사용하게 해준다.

 

문자를 예로 들면 'a', 'c', 'w'와 같이 본질적 상태와 폰트, 크기 등의 부가적인 상태로 이루어져 있다.

문자처럼 플라이급 객체는 본질적 상태를 저장하고 그 수가 적기 때문에 공유할 수 있게 된다.

 

flyweight 풀

플라이급 객체를 모아둔 것을 flyweight 풀이라고 하며 사용자(문서 편집기)는 공유된 풀의 객체를 참조하는 참조자를 갖도록 한다. 따라서 문서는 각 글자들이 나타날 때마다 참조자를 통해 해당 문자 인스턴스를 나타내게 할 수 있다.

 

예시 2) Glyph

Glyph

그래픽 객체를 나타내는 추상 클래스 Glyph는 플라이급 객체가 될 수 있다.

Character 클래스가 플라이급 객체의 예로 글자 정보를 본질 상태로 저장하고 외부 상태 Context에 따라 Draw연산을 수행할 수 있게 한다.

 

3. 참여자

구조 패턴 - 플라이급

  • Flyweight: 플라이급 객체가 부가적 상태에 따라 동작할 수 있는 인터페이스를 제공한다.
  • ConcreteFlyWeight: Flyweight의 인터페이스를 구현하고 내부적으로 갖고 있는 본질적 상태를 정의한다.
  • UnsharedConcreteFlyWeight: 모든 Flyweight의 파생 클래스가 공유될 필요는 없다.
  • FlyweightFactory: 플라이급 객체를 생성하고 관리하고 플라이급 객체가 제대로 공유되는 것을 보장한다.
  • Client: 플라이급 객체에 대한 참조자를 관리하고 플라이급 객체의 부가적 상태를 저장한다.

- FlyweightFactory의 GetFlyweight(key) 연산

사용자가 요청하는 플라이급 객체가 풀에 이미 있다면 반환하고 아니면 그 때 생성하여 풀에 추가하고 반환한다. 

 

4. 협력 방법

  • 사용자는 플라이급 객체에 바라는 동작에 해당하는 외부 상태를 매개변수로 해서 전달한다.
  • 사용자는 플라이급 객체의 인스턴스를 직접 만들 수 없고 FlyweightFactory를 통해서 얻어 참조하도록 한다.

5. 결과

  • 공유해야 하는 인스턴스 전체 수를 줄일 수 있음
  • 객체별 본질적 상태의 양을 줄일 수 있음
  • 부가적인 상태는 연산되거나 저장될 수 있음

- 복합체 패턴과 조합하여 그래프와 같이 계층적 구조를 모델링하는데 사용한다.

 

6. 구현

1) 부가적 상태를 플라이급 객체에 저장하지 말 것

  플라이급 객체를 표현 하는 최소 상태만 남겨두고 객체 사이즈를 작게 한다.

 

2) 공유할 객체를 관리

 FlyweightFactory를 통해서 인스턴스를 생성하고 참조를 통해 플라이급 객체를 공유할 수 있게 한다.

 

예시 코드

 

Glyph.h

Glyph 객체는 복합체 패턴으로 만들고 폰트 속성을 가지는 그래픽 요소를 위한 추상 클래스

class Glyph
{
public:
	virtual ~Glyph();

	virtual void Draw(Window*, GlyphContext&);

	// Font 속성을 위한 Set, Get 메서드
	virtual void SetFront(Font*, GlyphContext&);
	virtual Font* GetFont(GlyphContext&);

	// Glyph는 Composite 패턴으로 구현
	virtual void First(GlyphContext&);
	virtual void Next(GlyphContext&);
	virtual bool IsDone(GlyphContext&);
	virtual Glyph* Current(GlyphContext&);

	virtual void Insert(Glyph*, GlyphContext&);
	virtual void Remove(GlyphContext&);
protected:
	Glyph();
};

 

Character.h

Character 서브 클래스는 문자 코드를 저장하는 클래스

class Character : public Glyph
{
public:
	Character(char);
	virtual void Draw(Window*, GlyphContext&);
private:
	char _charcode;
};

 

GlyphContext.h

GlyphContext에 Glyph를 표현하는 외부 상태, 폰트를 저장하고 glyph 위치에 대한 폰트를 맵핑하는 BTree 자료구조를 관리한다.

Glyph는 복합체 패턴으로 트리 형식으로 순회가 가능하기 때문에 현재 Glyph 위치를 GlyphContext도 알고 있어야한다.

따라서 Glyph의 Next()는 GlyphContext의 Next()와 같이 연동된다.

GetFont() 연산에서 현재 글리프 위치에 대한 폰트를 찾을 때까지 BTree를 탐색하게 된다.

class GlyphContext
{
public:
	GlyphContext();
	virtual ~GlyphContext();
	// glyph가 사용될 때마다 GlyphContext의 _index를 수정 
	virtual void Next(int step = 1);
	virtual void Insert(int quantity = 1);
	// glyph 부가적 상태를 저장
	virtual Font* GetFont();
	virtual void SetFont(Font*, int span = 1);
private:
	// 현재 Glyph 위치
	int _index;
	// Glyph와 Font 사이의 대응 정보를 관리
	BTree* _fonts;
};

 

GlyphFactory.h

GlyphFactory는 Character 글리프에 대한 참조자를 관리하고 필요할 때마다 Character 글리프를 생성하도록 한다.

비문자 글리프 Row, Column은 호출시 생성만하고 공유되지는 않는다.

const int NCHARCODES = 128;
class GlyphFactory
{
public:
	GlyphFactory()
	{
		for (int i = 0; i < NCHARCODES; ++i)
			_character[i] = 0;
	}
	virtual ~GlyphFactory();
	virtual Character* CreateCharacter(char c)
	{
		if (!_character[c])
		{
			_character[c] = new Character(c);
		}
		return _character[c];
	}
	virtual Row* CreateRow()
	{
		return new Row;
	}
	virtual Column* CreateColumn()
	{
		return new Column;
	}
private:
	Character* _character[NCHARCODES];
};