[디자인패턴] 구조패턴(7) - Flyweight Pattern (플라이웨이트 패턴)

Study/Software Architecture · 2023. 6. 6. 13:43

디자인 패턴 총정리 시리즈의 열두 번째 패턴은 Flyweight Pattern입니다.

Flyweight 패턴은 여러 객체가 가지고 있는 내제적인 데이터를 공유하여 메모리 소비를 최소화하는 솔루션을 제공합니다.

해당 게시물에서 이러한 Flyweight 패턴에 대한 간단한 개념과 사용 예시, 장/단점을 알아보겠습니다.

 

1. 개요

Flyweight 패턴은 구조패턴 중 하나로써 여러 인스턴스 사이에 공통 상태를 공유하여 객체의 메모리 사용량을 줄일 수 있습니다.

객체의 내재적(공유)인 데이터와 외재적(고유) 데이터를 분리하고, 내재 데이터는 여러 객체 간에 공유되고 외재 데이터는 각 객체에 별도로 저장됩니다.

이러한 구조에서 Flyweight 패턴은 내재적 데이터를 다른 객체와 공유하여 메모리 사용량을 줄일 수 있습니다.

해당 패턴은 Flyweight와 FlyweightFactory, ConcreteFlyweight, UnsharedConcreteFlyweight, Client 5가지 주요 컴포넌트로 구성되어 있습니다.

 

2. 예시

다음은 Flyweight 패턴을 사용한 게임 개발 중 타입 맵을 생성하는 시나리오를 생각해보겠습니다.

타일 맵은 많은 수의 타일로 구성되어 있으며, 각 타일은 texture와 position 정보를 가지고 있습니다. 이때 durability는 각 타일의 고유한 데이터입니다.

여기서 Flyweight 패턴을 사용하여 공유 데이터와 개별 데이터를 효율적으로 관리할 수 있는 코드 구현 예시를 살펴보겠습니다.

#include <iostream>
#include <unordered_map>
#include <string>

// Flyweight interface
class Tile {
public:
    virtual void draw(int x, int y) = 0;
};

// Concrete Flyweight representing shared tile data
class SharedTile : public Tile {
private:
    std::string texture; // Intrinsic state: shared property
    
public:
    SharedTile(const std::string& texture) : texture(texture) {}

    void draw(int x, int y) override {
        std::cout << "Drawing tile with texture '" << texture << "' at position (" << x << ", " << y << ")" << std::endl;
    }
};

// Unshared Concrete Flyweight representing unique tile data
class UnsharedTile : public Tile {
private:
    std::string texture; // Extrinsic state: unique property
    int durability; // Extrinsic state: unique property
    
public:
    UnsharedTile(const std::string& texture, int durability)
        : texture(texture), durability(durability) {}

    void draw(int x, int y) override {
        std::cout << "Drawing unique tile with texture '" << texture << "' and durability " << durability << " at position (" << x << ", " << y << ")" << std::endl;
    }
};

// Flyweight Factory
class TileFactory {
private:
    std::unordered_map<std::string, Tile*> tilePool;

public:
    Tile* getTile(const std::string& texture, bool shared) {
        // Check if the tile already exists in the pool
        if (tilePool.find(texture) == tilePool.end()) {
            // If not, create a new tile object and add it to the pool
            Tile* tile = shared ? new SharedTile(texture) : new UnsharedTile(texture, rand() % 100);
            tilePool[texture] = tile;
        }
        return tilePool[texture];
    }
};

// Client
int main() {
    TileFactory tileFactory;

    const int mapWidth = 10;
    const int mapHeight = 10;

    std::string tileTextures[] = { "grass", "water", "rock", "sand" };

    // Generate random tile map
    for (int y = 0; y < mapHeight; ++y) {
        for (int x = 0; x < mapWidth; ++x) {
            const std::string& texture = tileTextures[rand() % 4];
            Tile* tile = tileFactory.getTile(texture, true); // Get shared tile
            tile->draw(x, y);
        }
    }

    // Add unique tiles
    Tile* uniqueTile1 = tileFactory.getTile("special", false);
    uniqueTile1->draw(3, 3);

    Tile* uniqueTile2 = tileFactory.getTile("special", false);
    uniqueTile2->draw(5, 5);

    return 0;
}

해당 예시에서는 Flyweight 객체인 Tile 인터페이스가 있습니다. SharedTile 및 UnsharedTile 클래스는 draw() 메서드를 구현하는 ConcreteFlyweight 객체입니다.

TileFacotry 클래스는 FlyweightFactory 역할을 하며, Flyweight 패턴에 속한 객체 풀을 유지 및 관리합니다.

이러한 구조를 통해 Client는 Factory에서 Tile 객체를 요청하고 draw()와 shared option을 사용해서 객체의 동작을 관리할 수 있습니다. 

 

 

3. 장점

  1. Reduced Memory Footprint
    : 여러 유사 객체 간에 데이터를 공유함으로써 메모리 소비를 줄여줍니다.
  2. Improved Performance
    : 메모리 사용량을 줄이기 때문에 프로그램의 전반적인 성능을 향상합니다. 또한, 객체의 생성 및 소멸에 필요한 시간을 최소화할 수 있습니다.
  3. Enhanced Scalability
    : 대규모 개체 집합을 효율적으로 관리할 수 있으므로, 방대한 양의 데이터를 처리하는 프로그램을 쉽게 확장시킬 수 있습니다.

 

4. 단점

  1. Increased Complexity
    : 해당 패턴을 구현하려면 내재 데이터와 외재 데이터를 분리해야 하므로 코드 복잡성을 높일 수 있습니다. 
  2. Thread Safety Considerations
    : 여러 스레드가 SharedFlyweight 객체에 동시에 접근하는 경우 스레드 안정성을 보장해야 합니다.

 

5. 결론

Flyweight 패턴은 공유 내제 데이터를 가진 많은 수의 객체를 처리하는 프로그램에서 메모리 사용을 최적화할 수 있는 솔루션을 제공해 줍니다. 하지만, 메모리 최적화와 코드 복잡성 증가 사이의 절충점을 고려하여 구조를 설계해야 합니다.실제로 사용한다면, 여러 컴포넌트에서 같은 객체를 참조하려고 할 때 스레드 안정성 문제가 생길 수 있을 것 같습니다. 이러한, 문제를 해결하기 위해 사전에 스레드 안정성을 높일 수 있는 장치를 해야 할 것 같습니다.

반응형