의도
- 공유를 통해 많은 수의 소립 객체들을 효과적으로 지원함
언제 쓰는가 ?
- 응용프로그램이 대량의 객체를 사용해야 할 때
- 객체의 수가 너무 많아져 저장 비용이 너무 높아질 때
- 대부분의 객체 상태를 부가적인 것으로 만들 수 있을 때
- 부가적인 속성들을 제거한 후 객체들을 조사해 보니 객체의 많은 묶음이 비교적 적은 수의 공유된 객체로 대체될 수 있을 때. 현재 서로 다른 객체로 간주한 이유는 이들 부가적인 속성 때문이었지 본질이 달랐던 것은 아닐 때
- 응용프로그램이 객체의 정체성에 의존하지 않을 때. 플라이급 객체들은 공유될 수 있음을 의미하는데, 식별자가 있다는 것은 서로 다른 객체로 구별해야 한다는 의미이므로 플라이급 객체를 사용할 수 없음
구조
- Flyweight : Flyweight가 받아들일 수 있고, 부가적 상태에서 동작해야하는 인터페이스를 선언함.
- ConcreteFlyweight : Flyweight 인터페이스를 구현하고 내부적으로 갖고 있어야 하는 본질적 상태에 대한 저장소를 정의. ConcreteFlyweight 객체는 공유할 수 있는 것이어야 함. 그러므로 관리하는 어떤 상태라도 본질적인 것이어야 함.
- UnsharedConcreteFlyweight : 모든 플라이급 서브클래스들이 공유될 필요는 없음. Flyweight 인터페이스는 공유를 가능하게 하지만, 그것을 강요해서는 안 됨. UnsharedConcreteFlyweight 객체가 ConreteFlyweight 객체를 자신의 자식으로 갖는 것은 흔한 일임.
- FlyweightFactory : 플라이급 객체를 생성하고 관리하며, 플라이급 객체가 제대로 공유되도록 보장함. 사용자가 플라이급 객체를 요청하면 FlyweightFactory 객체는 이미 존재하는 인스턴스를 제공하거나 만약 존재하지 않는다면 새로 생성함.
- Client : 플라이급 객체에 대한 참조자를 관리하며 플라이급 객체의 부가적 상태를 저장함.
협력 방법
- 플라이급 객체가 기능을 수행하는 데 필요한 상태가 본질적인 것인지 부가적인 것인지를 구분해야 함. 본질적인 상태는 ConcreteFlyweight에 저장, 부가적인 상태는 사용자가 저장하거나, 연산되어야 하는 다른 상태로 관리해야 함. 사용자는 연산을 호출할 때 자신에게만 필요한 부가적인 상태를 플라이급 객체에 매개변수로 전달함.
- 사용자는 ConcreteFlyweight의 인스턴스를 직접 만들 수 없음. 사용자는 ConcreteFlyweight 객체를 FlyweightFactory 객체에서 얻어야 함. 이렇게 해야 플라이급 객체가 공유될 수 있음.
결과
- 공유해야 하는 인스턴스의 전체 수를 줄일 수 있음.
- 객체별 본질적 상태의 양을 줄일 수 있음.
- 부가적인 상태는 연산되거나 저장될 수 있음.
구현 / 고려사항
- 부가적 상태를 제외함
- 부가적 상태와 본질적 상태를 구분하는 것이 패턴의 핵심임. 저장소를 아낄 수 있음.
- 공유할 객체를 관리함
- 사용자가 직접 인스턴스를 만들게 하면 안됨. FlyweightFactory를 통해서 사용자가 플라이급 객체를 찾아내게 해야 함. FlyweightFactory는 연관 저장소를 써서 사용자 요청 시 없으면 새로 생성 후 반환, 있으면 그대로 반환하면 됨.
- Flyweight 수가 고정되었거나 그 수가 작다면 해제(메모리 삭제)는 고려하지 않아도 괜찮음.
예제 코드
- 책에 쓰여 있던 예제는 문서 편집기에서 글자를 본질적인 것, Font를 부가적인 것으로 구분하여 문자들에 대해 플라이급 객체로 만들고 각 문자에 부가적인 추가 속성으로 Font를 적용시켰음.
- 이번 예제는 실제 사용 예시와는 거리가 좀 멀지만 패턴 자체가 어떤 내용을 핵심으로 생각하는지를 위주로 구상하여 작성함.
- Flyweight는 FlyweightFactory를 통해 얻어올 수 있고 FlyweightCollector는 공유 객체인 Flyweight를 Factory로 부터 얻어와 Flyweight를 활용하여 각 Flyweight의 name을 hash로 출력하는 예제를 구성해봄.
#include "Flyweight1.h"
#include <iostream>
int main()
{
FlyweightFactory flyweight_factory;
FlyweightCollector flyweight_collector1("collector1");
FlyweightCollector flyweight_collector2("collector2");
Flyweight* flyweight1 = flyweight_factory.GetFlyweight("SomeName1");
Flyweight* flyweight2 = flyweight_factory.GetFlyweight("SomeName2");
Flyweight* flyweight3 = flyweight_factory.GetFlyweight("SomeName3");
flyweight_collector1.AddFlyweight(flyweight1);
flyweight_collector1.AddFlyweight(flyweight2);
flyweight_collector2.AddFlyweight(flyweight3);
flyweight_collector2.AddFlyweight(flyweight1);
flyweight_collector1.PrintAllFlyweightsHash();
std::cout << '\n';
flyweight_collector2.PrintAllFlyweightsHash();
return 0;
}
- 결과적으로 봤을 때 collector1은 flyweight1과 flyweight2를 출력하고 collector2는 flyweight3과 flyweight1을 출력하였고, 이 때 flyweight1에 대해선 같은 객체를 공유하고 있음. ( '같은 객체를 공유한다'가 핵심이라고 봄 )
//Flyweight1.h
#pragma once
#include <map>
#include <string>
#include <vector>
class Flyweight;
class FlyweightFactory
{
public:
Flyweight* GetFlyweight(std::string key);
~FlyweightFactory();
private:
std::map<std::string, Flyweight*> FlyweightMap;
};
class Flyweight
{
friend class FlyweightFactory;
private:
Flyweight(std::string name) : name(name) {}
public:
size_t GetHash();
std::string GetName() { return name; }
private:
std::string name;
std::hash<std::string> hash;
};
class FlyweightCollector
{
public:
FlyweightCollector(std::string name) : name(name) {}
public:
void AddFlyweight(Flyweight* flyweight);
void PrintAllFlyweightsHash();
private:
std::vector<Flyweight*> flyweights;
std::string name;
};
- FlyweightFactory, Flyweight, FlyweightCollector 3가지의 클래스를 작성함
- Factory는Flyweight를 제공하고, Flyweight는 공유될 수 있는 특성을 보유하며 (name을 통한 hash), Collector는 공유되는 Flyweight들을 여러 개 가질 수 있으며 그걸 확인해 볼 수 있는 메서드 PrintAllFlyweightsHash를 만들어 두었음
- 이 때 Flyweight 객체는 Factory를 통해서만 만들어질 수 있게 끔 생성자를 private으로 감추고 friend 선언을 해주었음(공유 객체니까 사용자가 직접 만들지 못하게끔 막았음)
//Flyweight1.cpp
#include "Flyweight1.h"
#include <iostream>
Flyweight* FlyweightFactory::GetFlyweight(std::string key)
{
if (FlyweightMap.find(key) == FlyweightMap.end())
{
FlyweightMap[key] = new Flyweight(key);
return FlyweightMap[key];
}
else
{
return FlyweightMap[key];
}
}
FlyweightFactory::~FlyweightFactory()
{
for (auto& fm : FlyweightMap)
{
if (fm.second != nullptr)
{
delete fm.second;
}
}
}
size_t Flyweight::GetHash()
{
return hash(name);
}
void FlyweightCollector::AddFlyweight(Flyweight* flyweight)
{
if (flyweight)
{
flyweights.push_back(flyweight);
}
}
void FlyweightCollector::PrintAllFlyweightsHash()
{
int count = 0;
for (auto& f : flyweights)
{
++count;
std::cout << name << "'s " << count << " Flyweight's Hash : " << f->GetHash() << " and Name : " << f->GetName() << '\n';
}
}
- 이 패턴의 핵심은 Factory의 GetFlyweight에 있는데 map에 존재하는 flyweight라면 그대로 반환하고 아니라면 새로 만들어서 반환하게 됨. 또한 사용자는 이 메서드를 통해서만 플라이급 객체 Flyweight에 접근할 수 있음.
'디자인 패턴 > 구조' 카테고리의 다른 글
프록시(Proxy) (0) | 2024.01.02 |
---|---|
퍼사드(Facade) (1) | 2023.12.22 |
장식자(Decorator) (0) | 2023.12.22 |
복합체(Composite) (2) | 2023.12.21 |
가교(Bridge) (2) | 2023.12.21 |