객체 지향/디자인 패턴

디자인 패턴 - Flyweight

ooknimm 2023. 10. 28. 21:36

디자인 패턴 중 Flyweight 패턴에 대해 설명합니다.


설명

메모리 사용량을 줄이기 위해 여러 객체 간 공통적으로 사용하는 상태를 분리하여 공유하는 패턴입니다. 즉, 여러 객체에서 재사용할 수 있는 부분을 캐싱하여 최적화하는 것입니다. 다음과 같은 상황에서 적용하면 유용합니다.

  • 애플리케이션이 많은 양의 유사한 객체를 필요로 합니다.
  • 그로 인해 메모리 사용량이 높습니다.
  • 이 객체들간 중복된 상태가 존재합니다. 이를 분리하여 여러 객체에 공유할 수 있습니다.

Flyweight 패턴은 객체에서 Mutable 데이터와 Immutable 데이터로 나누어 분리하게 됩니다. Mutable 데이터는 Extrinsic state로, Immutable 데이터는 Intrinsic state라고 불립니다.

  • Flyweight: repeating_state(Intrinsic state)를 저장하고 있는 객체입니다. 상황에 따라 실질적인 로직을 수행하는 메소드를 정의해도 되고 안 해도 됩니다. 메소드를 정의하는 경우 unique_state(Extrinsic state)를 파라미터로 전달받습니다. 주의할 점은 캐싱될 객체이기 때문에 최초 생성된 후 객체의 변경이 되어서는 안 됩니다.
  • FlyweightFactory: Flyweight 객체 생성과 객체 Pool을 관리하는 팩토리 클래스입니다. get_flyweight 팩토리 메소드는 repeating_state(Intrinsic state)를 파라미터로 전달받아 캐싱된 객체들 중 해당 상태를 포함하는 객체를 찾아 반환합니다. 만약 캐싱된 객체가 없다면 새로 객체를 생성하고 캐싱한 후 반환합니다. 객체 식별은 repeating_state(Intrinsic state)로 합니다. 
  • Context: 공유 자원인 Flyweight를 필드로 참조하고, unique_state(Extrinsic state)를 저장하는 객체입니다. 상황에 따라 실질적인 로직 수행을 Flyweight 클래스에 위임해도 되고 안 해도 됩니다. 위임하는 경우 Flywegith의 메소드를 호출할 때 unique_state(Extrinsic state)를 파라미터로 전달합니다.
  • Client: Context를 생성/사용/관리합니다. Context 생성자를 호출할 때 Flyweight를 파라미터로 넘겨줘야 합니다.

다음과 같은 장점이 있습니다.

  • 자원을 공유하여 메모리를 아낄 수 있습니다.

 

적용 예시

Flyweight 패턴을 사용해 숲을 그리는 추상적인 예제입니다.

from typing import List


class Forest:
    def __init__(self) -> None:
        self.trees: List["Tree"] = []

    def plant_tree(self, name: str, color: str, x: int, y: int):
        tree_type = TreeTypeFactory.get_tree_type(name, color)
        tree = Tree(x, y, tree_type)
        self.trees.append(tree)

    def draw(self): 
        for tree in self.trees:
            tree.draw()


class Tree:
    def __init__(self, x: int, y: int, type: "TreeType") -> None:
        self.x = x
        self.y = y
        self.type = type
    
    def draw(self):
        return self.type.draw(self.x, self.y)
    
    
class TreeType:
    def __init__(self, name: str, color: str) -> None:
        self.name = name
        self.color = color
        self.image = self.get_image(name, color)

    def get_image(self, name: str, color: str) -> bytes: ...

    def draw(x: int, y: int): ...
        # draw image at coordinates


class TreeTypeFactory:
    tree_types: List[TreeType] = []

    @classmethod
    def get_tree_type(self, name: str, color: str) -> TreeType:
        for tree_type in self.tree_types:
            if tree_type.name == name and tree_type.color == color:
                return tree_type
        tree_type = TreeType(name, color)
        self.tree_types.append(tree_type)

name, color, image 같은 공유자원을 TreeType(Flyweight)로 분리하여 캐싱합니다. 같은 시간대에 많은 양의 Tree 객체를 생성해도 메모리를 절약할 수 있습니다.


참조

https://refactoring.guru/design-patterns/flyweight