플라이웨이트 패턴
개요
문서에 글자 만 개가 있어도 글꼴과 크기는 몇 종류뿐일 수 있다. 글자마다 같은 스타일 객체를 새로 만들면 중복된 상태가 쌓인다.
플라이웨이트(Flyweight)는 함께 써도 되는 공통 상태를 공유한다. 객체 안에 공유하는 상태를 내재 상태, 위치처럼 사용할 때 달라지는 상태를 외재 상태라고 부르며, 예제에서는 스타일과 배치를 나눈다.
UML
StylePool이 같은 글꼴·크기의 GlyphStyle을 재사용한다. 각 PlacedGlyph는 스타일을 참조하지만 글자와 X 좌표는 자신이 가진다.
역사
GoF는 1994년 Design Patterns에서 Flyweight를 구조 패턴으로 정리했다. 많은 작은 객체에서 공통 상태를 공유해 저장 비용을 줄이는 데 쓰인다.1
왜 사용하는가?
공유할 수 있는 상태와 항목별 상태를 분리하면 같은 스타일을 재사용할 수 있다. 공유 객체를 수정하면 여러 항목이 함께 바뀌므로 예제의 GlyphStyle은 변경 불가로 둔다.
풀에 계속 새 키를 추가하면 풀 자체가 커진다. 보관 범위와 정리 정책을 정하고, 실제 메모리 절감이 필요한 부하인지 측정한 뒤 도입한다.
예제
같은 스타일을 두 번 요청했을 때 객체 참조도 같은지 확인한다.
using System;
using System.Collections.Generic;
using System.Linq;
var pool = new StylePool();
Console.WriteLine(ReferenceEquals(pool.Get("Serif", 16), pool.Get("Serif", 16)));
sealed record GlyphStyle(string Font, int Size);
sealed record PlacedGlyph(char Letter, int X, GlyphStyle Style);
sealed class StylePool
{
private readonly Dictionary<(string Font, int Size), GlyphStyle> styles = new();
public int Count => styles.Count;
public GlyphStyle Get(string font, int size)
{
var key = (font, size);
if (!styles.TryGetValue(key, out var style))
{
style = new GlyphStyle(font, size);
styles.Add(key, style);
}
return style;
}
}
True같은 값의 객체를 두 번 만든 뒤 비교하는 대신, StylePool이 이미 만든 객체를 돌려준다.
범위 넓힌 예제
다른 위치에 놓인 두 글자가 같은 스타일을 참조한다. 위치를 스타일에 넣지 않아 공유를 유지한다.
using System;
using System.Collections.Generic;
using System.Linq;
var pool = new StylePool();
var letters = new[]
{
new PlacedGlyph('A', 0, pool.Get("Serif", 16)),
new PlacedGlyph('B', 20, pool.Get("Serif", 16))
};
foreach (var letter in letters) Console.WriteLine(letter.Letter + "@" + letter.X);
Console.WriteLine("styles: " + pool.Count);
sealed record GlyphStyle(string Font, int Size);
sealed record PlacedGlyph(char Letter, int X, GlyphStyle Style);
sealed class StylePool
{
private readonly Dictionary<(string Font, int Size), GlyphStyle> styles = new();
public int Count => styles.Count;
public GlyphStyle Get(string font, int size)
{
var key = (font, size);
if (!styles.TryGetValue(key, out var style))
{
style = new GlyphStyle(font, size);
styles.Add(key, style);
}
return style;
}
}
A@0
B@20
styles: 1글자와 좌표는 서로 달라도 스타일은 하나다. 싱글턴처럼 전체 종류를 하나로 제한하는 구조가 아니라, 필요한 스타일별로 공유 대상을 관리한다. 이 코드는 한 스레드에서 쓰는 풀이다.
같이 보기
각 예제는 독립된 .NET 10 콘솔 프로그램이다.