플라이웨이트 패턴(Flyweight Pattern)

  • 플라이웨이트 패턴은 이름이 의미하는 바와 같이 공통된 객체를 공유하여 자원을 절약하는 패턴입니다.
  • 자원이란 컴퓨터의 메모리를 의미합니다.

플라이웨이트 패턴 구조

  • 공통된 객체를 공유하기 위해서는 특별한 조건이 필요합니다.
  • 공유하려는 객체는 불필요한 메모리 사용을 방지하기 위해 단 하나의 객체로 만들어야 합니다.
  • 이러한 특징은 **싱글톤 패턴(Singleton Pattern)**과 매우 비슷합니다.
  • 또한 객체의 생성은 new 키워드로 동작합니다.
  • 객체를 싱글톤 패턴과 결합하여 생성하는 것은 **팩토리 패턴(Factory Pattern)**과 연관 지어 생각할 수 있습니다.
  • 이처럼 플라이웨이트 패턴은 다른 객체와의 결합이 많은 편입니다.

객체 공유 저장소

  • 객체를 공유한다는 것은 동일한 객체를 생성하고, 이를 재사용하는 것을 의미합니다.
  • 싱글톤 패턴으로 동일한 객체를 생성할 수 있습니다.
  • 하지만 보다 편리하게 관리하기 위해서 생성한 객체들을 관리하는 공유 저장소를 만듭니다.
  • 특정 객체를 호출하면 공유 저장소에서 동일한 객체가 있는지 확인하고, 있다면 이미 만들어진 객체를 반환합니다.
  • 이러한 일련의 작업을 수행할 팩토리 패턴에서 저장소 역할을 담당합니다.

플라이웨이트 패턴의 상태 구분

  • 객체는 공유된 상태에 따라 두 가지로 구분합니다.
    • Shared, 본질적(Intrinsic) 상태: 공유 객체의 상태 값(프로퍼티)에 따라 달라지지 않고 동일하게 적용되는 상태.
    • Unshared, 부가적(Extrinsic) 상태 공유 객체가 상태 값에 종속적인 상태.
      • 객체의 특정 데이터값을 변경하여 이를 참조하는 다른 객체에 영향을 미치는 상태를 의미합니다.

플라이웨이트 패턴: 모스부호 변환기

  • 플라이웨이트 패턴을 활용한 예제를 하나 만들어보겠습니다.
  • 특정한 문자열을 입력받으면, 해당 문자열을 모스부호로 변환하는 예제입니다.
  • 반복되는 문자열의 특성상 동일한 역할의 객체를 여러 개 만들 필요 없이, 공유 객체를 활용하여 문제를 풀어보도록 하겠습니다.

플라이웨이트 패턴: 플라이웨이트 인터페이스 클래스

  • 공유 객체들을 동일한 방법으로 호출하기 위해 인터페이스 클래스를 작성합니다.
// 인터페이스
class Flyweight
{
public:
    virtual void code() = 0;
};

플라이웨이트 패턴: 플라이웨이트 클래스 구현

  • 인터페이스를 적용하는 클래스를 작성합니다.
class MorseG :
    public Flyweight
{
public:
    MorseG()
    {
        std::cout << "[MorseG] object has concreated" << std::endl;
    }

    void code() override
    {
        std::cout << "**-* ";
    }
};

class MorseO :
    public Flyweight
{
public:
    MorseO()
    {
        std::cout << "[MorseO] object has concreated" << std::endl;
    }

    void code() override
    {
        std::cout << "--- ";
    }
};

class MorseL :
    public Flyweight
{
public:
    MorseL()
    {
        std::cout << "[MorseL] object has concreated" << std::endl;
    }

    void code() override
    {
        std::cout << "*-** ";
    }
};

class MorseE :
    public Flyweight
{
public:
    MorseE()
    {
        std::cout << "[MorseE] object has concreated" << std::endl;
    }

    void code() override
    {
        std::cout << "* ";
    }
};

플라이웨이트 패턴: 팩토리 구현

  • 공유 객체를 생성하고, 관리할 팩토리를 작성합니다.
  • 문자열을 이용해 객체 생성을 분리하였습니다.
class Factory
{
public:
    std::shared_ptr<Flyweight> getCode(int key)
    {
        if (m_list.find(key) == m_list.end())
        {
            if (key == 'g')
            {
                m_list[key] = std::make_shared<MorseG>();
            }

            if (key == 'o')
            {
                m_list[key] = std::make_shared<MorseO>();
            }

            if (key == 'l')
            {
                m_list[key] = std::make_shared<MorseL>();
            }

            if (key == 'e')
            {
                m_list[key] = std::make_shared<MorseE>();
            }
        }

        return m_list[key];
    }

private:
    // 객체 공유 저장소
    std::map<int, std::shared_ptr<Flyweight>> m_list;
};

플라이웨이트 패턴: 실행 코드

  • 적절한 모스부호를 입력하고 결과를 출력합니다.
int main(const int argc, const char* argv[])
{
    std::string name = "goooogllleee";
    std::cout << "Original name: " << name << std::endl;

    std::shared_ptr<Factory> factory = std::make_shared<Factory>();

    for (int i = 0; i < name.size(); i++)
    {
        std::cout << name[i] << "-> ";
        factory->getCode(name[i])->code();
        std::cout << std::endl;
    }

    return 0;
}
  • 실행 결과
Original name: goooogllleee
g-> [MorseG] object has concreated
**-*
o-> [MorseO] object has concreated
---
o-> ---
o-> ---
o-> ---
g-> **-*
l-> [MorseL] object has concreated
*-**
l-> *-**
l-> *-**
e-> [MorseE] object has concreated
*
e-> *
e-> *
  • 생성자를 출력하여 줄 바꿈이 어색하지만, 동일한 문자가 입력되어도 추가적인 객체를 생성하지 않는 것을 확인할 수 있습니다.

플라이웨이트 패턴: (참고) 템플릿 싱글톤을 적용한 팩토리

  • 템플릿 싱글톤을 활용한 팩토리의 구현입니다.
template<typename T>
class Singleton
{
public:
    static std::shared_ptr<T> getInstance()
    {
        if (m_instance == nullptr)
        {
            m_instance = std::make_shared<T>();
            atexit(destroy);
        }

        return m_instance;
    }

protected:
    Singleton()
    {

    }

    virtual ~Singleton()
    {

    }

    Singleton(const Singleton&)
    {

    }

private:
    static void destroy()
    {
        delete m_instance;
    }

    static std::shared_ptr<T> m_instance;
};

template <typename T> std::shared_ptr<T> Singleton <T>::m_instance;

class TemplateFactory :
    public Singleton<TemplateFactory>
{
public:
    template <typename T>
    std::shared_ptr<Flyweight> getCode(int key)
    {
        if (m_list.find(key) == m_list.end())
        {
            m_list[key] = std::make_shared<T>();
        }
    }

private:
    std::map<int, std::shared_ptr<Flyweight>> m_list;
};