Search

플라이 웨이트 패턴 (디자인 패턴)

플라이 웨이트 패턴

플라이웨이트 패턴의 핵심 개념
객체의 재사용
메모리 사용 최적화
객체 생성의 효율성
플라이웨이트 패턴은 재사용 가능한 객체 인스턴스를 공유시켜 메모리 사용량을 최소화하는 구조 패턴
FlyweightFactory를 활용하여 Flyweight 객체의 생성, 관리, 공유를 담당하는 컴포넌트를 정의하며 패턴의 실제 구현을 더 체계적으로 만들게 됩니다
Flyweight (플라이웨이트):
경량 객체를 묶는 인터페이스
ConcreteFlyweight (구체적 플라이웨이트):
공유 가능하여 재사용되는 객체
내부 상태(intrinsic state)를 가짐
UnsharedConcreteFlyweight (비공유 구체적 플라이웨이트):
공유 불가능한 객체
외부 상태(extrinsic state)를 가짐
FlyweightFactory (플라이웨이트 팩토리):
경량 객체를 생성하는 공장 역할
Flyweight 객체를 관리하는 캐시 역할
GetFlyweight() 메서드:
팩토리 메서드 역할 수행
객체가 메모리에 존재하면 그대로 반환
객체가 없으면 새로 생성하여 반환
Client (클라이언트):
FlyweightFactory를 통해 Flyweight 타입의 객체를 얻어 사용
다시 설명해보자면, 많은 수의 유사한 객체를 효율적으로 공유하여 메모리 사용을 최적화하는 기법
이 패턴의 핵심 아이디어는 객체의 내부 상태를 intrinsic state와 extrinsic state 상태로 분리
고유 상태 Intrinsic state: 값이 고정되어 공유할 수 있는 객체
언제 어디서든 공유 가능
외적 상태 Extrinsic state : 매번 값이 바뀌어 공유할 수 없는 객체
캐시하여 공유 불가
내재적 상태를 격리하고 여러 객체 간에 공유하면 메모리 소비를 크게 줄일 수 있습니다.
각 인스턴스에 고유한 외재적 상태는 독립적으로 관리할 수 있습니다.
모든 객체를 일일히 인스턴스화 하지않고 재사용할수 있는 객체는 재사용함으로써 메모리를 가볍게 만든다는 의미

예제 : 폭탄

문제 : 폭탄 들은 모두 하나의 객체일 것인데, 이 폭탄 객체들을 일일히 인스턴스화하면 객체를 생성 할때마다 메모리를 차지하게 되어 게임이 무거워질 것
폭탄의 특성
본질적(내부) 상태: 폭탄 모양, 폭탄 색깔
비본질적(외부) 상태: 폭탄의 x, y 좌표
플라이웨이트 패턴 적용:
ConcreteFlyweight: 폭탄 모양, 색깔 (고정 정보)
캐시하여 여러 곳에서 공유 가능
UnsharedConcreteFlyweight: 폭탄의 x, y 좌표 (변화 정보)
실시간으로 변화하므로 공유 불가능
FlyweightFactory:
폭탄 객체 생성
캐싱 및 관리 담당

장단점 정리

사용 시기
1.
생성되는 객체의 수가 많아 저장 비용이 높을 때
2.
객체가 메모리에 오래 상주하며 자주 사용될 때
3.
공통 인스턴스를 많이 생성하는 로직이 있을 때
4.
메모리 사용을 최소화해야 하는 경우 (예: 임베디드 시스템)
장점
1.
메모리 사용량 감소
2.
프로그램 속도 향상
객체 생성 및 메모리 적재 시간 절약
공유 객체 사용으로 인한 효율성 증가
단점
1.
코드 복잡도 증가

게임 예제

오픈소스보다는 실제로 객체를 많이 사용하는 프로젝트들이 사용하는 패턴
예) 게임
대량의 총알, 미사일, 군인이 등장하는 게임이라고 가정해보자.
클래스 다이어그램
classDiagram
    class Bullet {
        +float latitude
        +float longitude
        +__init__(latitude: float, longitude: float)
    }
    class Missile {
        +float latitude
        +float longitude
        +__init__(latitude: float, longitude: float)
    }
    class Soldier {
        +float latitude
        +float longitude
        +__init__(latitude: float, longitude: float)
    }
    class MapObject {
        +str type
        +__init__(type_: str)
        +__str__() str
    }
    class MapObjectFactory {
        -Dict~str, MapObject~ _type_object
        +get_map_object(type_: str) MapObject
    }
    MapObjectFactory ..> MapObject : creates
    
    class Client {
        +main()
    }
    Client ..> MapObjectFactory : uses
    Client ..> Bullet : creates
    Client ..> Missile : creates
    Client ..> Soldier : creates
    
    class generate_map_objects {
        +generate_map_objects() Dict
    }
    Client ..> generate_map_objects : calls
Mermaid
복사
대량의 맵 객체(총알, 미사일, 군인)를 효율적으로 관리
MapObject 클래스
공유 가능한 내부 상태(type)를 가진 플라이웨이트 객체
MapObjectFactory 클래스
플라이웨이트 객체를 생성하고 관리하는 팩토리 클래스
MapObjectFactory 클래스는 이미 생성된 MapObject 인스턴스를 재사용
class MapObject: def __init__(self, type_: str): # 추가된 내부 상태 self.type = type_ self.size = self._get_size(type_) self.color = self._get_color(type_) self.speed = self._get_speed(type_) def __str__(self): return f"{self.type} (Size: {self.size}, Color: {self.color}, Speed: {self.speed})" # setter들 def _get_size(self, type_: str) -> str: sizes = {"bullets": "작음", "missiles": "대빵 큼", "soldiers": "중간"} return sizes.get(type_, "unknown") def _get_color(self, type_: str) -> str: colors = {"bullets": "은색", "missiles": "녹색", "soldiers": "갈색"} return colors.get(type_, "unknown") def _get_speed(self, type_: str) -> str: speeds = {"bullets": "겁나 빠름", "missiles": "그냥 빠름", "soldiers": "느려터짐"} return speeds.get(type_, "unknown") class MapObjectFactory: _type_object: Dict[str, MapObject] = {} # 새로운 타입의 MapObject가 요청될 때만 새 인스턴스를 생성하고, 그 외에는 기존 인스턴스를 반환 @classmethod def get_map_object(cls, type_: str) -> MapObject: if type_ not in cls._type_object: cls._type_object[type_] = MapObject(type_) return cls._type_object[type_]
Python
복사
Bullet, Missile, Soldier 클래스
각 객체의 외부 상태(위도, 경도)를 관리
class Bullet: def __init__(self, latitude: float, longitude: float): self.latitude = latitude self.longitude = longitude class Missile: def __init__(self, latitude: float, longitude: float): self.latitude = latitude self.longitude = longitude class Soldier: def __init__(self, latitude: float, longitude: float): self.latitude = latitude self.longitude = longitude
Python
복사
generate_map_objects 함수
대량의 객체 데이터를 생성
def generate_map_objects() -> Dict[str, Dict[int, Dict[str, float]]]: return { "bullets": { id_: {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} for id_ in range(1000000) }, "missiles": { id_: {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} for id_ in range(1000000) }, "soldiers": { id_: {"latitude": random.uniform(-90, 90), "longitude": random.uniform(-180, 180)} for id_ in range(1000000) }, }
Python
복사
main 함수
대량의 객체를 생성하면서 플라이웨이트 패턴을 적용하여 메모리 사용을 최적화
def main(): map_objects = generate_map_objects() map_object_factory = MapObjectFactory() for object_type, objects in map_objects.items(): for object_id, object_info in objects.items(): if object_type == "bullets": Bullet(**object_info) elif object_type == "missiles": Missile(**object_info) elif object_type == "soldiers": Soldier(**object_info) # 동일한 type을 가진 MapObject 인스턴스를 재사용함 map_object_factory.get_map_object(object_type) print(f"Number of map objects: {len(map_objects) * 1000000}") print(f"Number of MapObject instances: {len(MapObjectFactory._type_object)}")
Python
복사
⇒ 3백만 개의 맵 객체를 생성하지만, 실제로는 단 3개의 MapObject 인스턴스만 생성됩니다.