본문 바로가기

Programming/Design pattern

[Design pattern] 플라이웨이트(Flyweight)패턴

플라이웨이트 패턴
[출처] https://zetawiki.com/wiki/%ED%94%8C%EB%A0%88%EC%9D%B4%EC%9B%A8%EC%9D%B4%ED%8A%B8_%ED%8C%A8%ED%84%B4

 

플라이웨이트(Flyweight) 패턴

플라이웨이트 패턴은 동일하거나 유사한 객체들 사이에 가능한 많은 데이터들을 서로 공유하여 사용하도록 하여 메모리 사용량을 최소화하는 디자인 패턴입니다.

플라이웨이트 패턴은 아래와 같은 경우에 사용합니다.

  • 같은 속성을 가진 객체가 반복적으로 만들어 질 때
  • 이미지, 폰트, 게임 캐릭터 등 대량의 객체를 생성해야하는 경우
  • 데이터 중 일부만 변경되고 대부분 공통 속성을 공유할 수 있을 때

플라이웨이트 패턴은 다음과 같은 요소로 이루어져있습니다.

Flyweight (공유 객체)

자주 바뀌지 않는 공통 속성을 저장하는 객체입니다.

여러 객체들이 공유해서 사용하는 대상입니다.

 

Flyweight Factory

공유 객체를 생성하고 캐싱하는 클래스입니다.

요청이 들어오면 캐시에 동일할 객체가 있는지 확인하고 있으면 재사용, 없으면 생성해주는 역할을 합니다.

 

Context

실제 프로그램에서 쓰이는 객체입니다.

공유객체(Flyweight)를 내부에 포함하고 있고, 자신만의 고유한 상태도 함께 가지고 있습니다.

 

Client

클라이언트는 Context를 생성하면서 필요한 Flyweight를 팩토리에 요청합니다.

공유객체를 직접 만들지 않고 항상 팩토리를 통해 가져옵니다.

 

플라이웨이트 패턴 예제 : 폰트 렌더링 시스템

문서에서 수천 개의 문자를 출력해야하는데 같은 폰트,크기,스타일을 가지는 문자가 반복되기 때문에

매번 새 객체를 만들게 되면 메모리 낭비가 심해지는 상황입니다.

그래서 공통적인 속성은 공유하고, 개별 위치 정보만 다르게 관리하는 식으로 구현하려 합니다.

 

Flyweight (공유 객체)

class CharacterStyle:
    def __init__(self, font, size, bold=False, italic=False):
        self.font = font
        self.size = size
        self.bold = bold
        self.italic = italic

    def __str__(self):
        return f"{self.font}, {self.size}px, bold={self.bold}, italic={self.italic}"

위의 클래스는 공통 속성만 가지고 있는 클래스입니다. 텍스트의 위치나 값 같은 고유값들은 가지고 있지 않습니다.

 

Flyweight Factory

class StyleFactory:
    _cache = {}

    @classmethod
    def get_style(cls, font, size, bold=False, italic=False):
        key = (font, size, bold, italic)
        if key not in cls._cache:
            cls._cache[key] = CharacterStyle(font, size, bold, italic)
        return cls._cache[key]

위의 예제는 Flyweight Factory를 구현한 내용입니다. key를 통해 Flyweight를 관리하고 있습니다.

같은 속성의 스타일이 요청되면 캐싱해서 하나의 객체만 재사용합니다.

 

실제 객체 (Context, Flyweight)

class Character:
    def __init__(self, char, x, y, style: CharacterStyle):
        self.char = char      # 고유한 상태 (extrinsic)
        self.x = x
        self.y = y
        self.style = style    # 공유된 상태 (intrinsic)

    def render(self):
        print(f"'{self.char}' at ({self.x},{self.y}) - style: {self.style}")

실제 문자 객체입니다. 문자의 내용과 위치는 고유하게 가지고 있고, 스타일은 공유 상태를 사용합니다.

 

사용 예시

factory = StyleFactory()

style1 = factory.get_style("Arial", 12)
style2 = factory.get_style("Arial", 12)  # 같은 스타일 → 같은 객체 반환
style3 = factory.get_style("Arial", 14)  # 다른 크기 → 다른 객체

char1 = Character("A", 0, 0, style1)
char2 = Character("B", 1, 0, style2)
char3 = Character("C", 2, 0, style3)

char1.render()
char2.render()
char3.render()

# 'A' at (0,0) - style: Arial, 12px, bold=False, italic=False
# 'B' at (1,0) - style: Arial, 12px, bold=False, italic=False
# 'C' at (2,0) - style: Arial, 14px, bold=False, italic=False

위의 사용 예제에서 style1과 style2는 같은 스타일로 같은 객체를 참조하고 있습니다.

 

플라이웨이트 패턴 장/단점

장점

1. 중복 객체 생성을 줄이고, 공유하여 메모리 사용량이 감소됩니다.

2. 객체 생성 비용을 줄여서 전체적인 성능이 향상됩니다.

3. 고유 상태와 공유 상태를 명확히 구분할 수 있습니다.

 

단점

1. 캐시, 공유상태 관리 등 부가적인 로직 구현이 필요하여 구현복잡도가 증가합니다.

2. 객체가 공유되기 때문에 의도치 않게 값이 섞일 수 있습니다.

3. 공유 가능성이 낮은 경우에는 오히려 관리 오버헤드만 생길 수 있기 때문에 적절한 상황에 사용해야 합니다.

 

플라이웨이트 패턴은 많은 수의 유사 객체가 반복 생성될 때,
공통 속성을 공유하여 메모리 사용을 줄이는 데 유용한 패턴
입니다.

특히 렌더링 시스템, 텍스트 에디터, 게임 엔진 등에서 자주 활용됩니다.