C++ - 플라이웨이트 패턴 (Flyweight Pattern)
Architecture & Design Pattern 전체 링크
참고
플라이웨이트 패턴 (Flyweight Pattern) - 구조 패턴
- 객체를 공유하여 메모리 사용량을 줄이고 성능을 향상시키기 위한 패턴
- 많은 수의 유사한 객체를 생성해야 할 때 사용된다.
구현
- Flyweight Interface : 플라이웨이트 객체들이 구현하는 인터페이스를 정의, 외부에서 사용될 메서드를 포함한다.
- Flyweight Factory : 플라이웨이트 객체들을 생성하고 관리, 객체를 생성하거나 이미 생성된 객체를 재사용한다.
- Flyweight : 중복 생성되는 객체의 부분을 나타내는 클래스, 외부와 내부로 나뉘며, 내부 상태는 공유된다.
장점
- 중복되는 객체를 공유하여 메모리 사용량을 줄인다.
- 이미 생성된 객체를 공유함으로써 객체를 생성하는 비용을 줄인다.
단점
- 객체의 내부 상태를 변경할 수 없다.
- 플라이웨이트 팩토리와 클라이언트 코드가 추가되어 복잡도가 증가한다.
벽 만들기
벽에는 색깔, 무게, 질감과 같은 정보가 들어있다.
따라서 벽은 Type 클래스를 가지고 있다.
class Type
{
public:
Type(const string& c, int w, const string& t) : color(c), weight(w), texture(t) {}
void printType() { cout << color << ", " << weight << "kg, " << texture << endl; }
private:
string color;
int weight;
string texture;
};
그리고 이 벽을 (x, y) 좌표에 위치시켜야 한다.
따라서 벽은 Point2D 클래스도 가진다.
class Point2D
{
public:
Point2D(int _x, int _y) : x(_x), y(_y) {}
void printPoint() { cout << "Point : (" << x << ", " << y << ")" << endl; }
private:
int x;
int y;
};
벽 클래스는 다음과 같이 정의할 수 있다.
벽을 구성하는 Type과 Point2D의 메모리를 확인하기 위해 getType과 getPoint도 추가하였다.
class Wall
{
public:
Wall(Type t, Point2D p) : type(t), point(p) {}
Type* getType() { return &type; }
Point2D* getPoint() { return &point; }
private:
Type type;
Point2D point;
};
이제 많은 벽을 만들어야 한다고 가정하자.
좌표를 더해서 2로 나누어 떨어지면 파랗고 10kg의 벽돌을 해당 좌표에 만들고,
나누어 떨어지지 않으면 빨갛고, 30kg의 강철을 설정된 좌표에 만들자.
for (int r = 0; r < 3; r++)
{
for (int c = 0; c < 3; c++)
{
if ((r + c) % 2 == 0)
{
Type type("blue", 10, "brick");
Point2D point(r, c);
Wall* w = new Wall(type, point);
walls.push_back(w);
}
else
{
Type type("red", 30, "steel");
Point2D point(r, c);
Wall* w = new Wall(type, point);
walls.push_back(w);
}
}
}
아래 예시 코드를 실행해 보자.
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Type
{
public:
Type(const string& c, int w, const string& t) : color(c), weight(w), texture(t) {}
void printType() { cout << color << ", " << weight << "kg, " << texture << endl; }
private:
string color;
int weight;
string texture;
};
class Point2D
{
public:
Point2D(int _x, int _y) : x(_x), y(_y) {}
void printPoint() { cout << "Point : (" << x << ", " << y << ")" << endl; }
private:
int x;
int y;
};
class Wall
{
public:
Wall(Type t, Point2D p) : type(t), point(p) {}
Type* getType() { return &type; }
Point2D* getPoint() { return &point; }
private:
Type type;
Point2D point;
};
int main()
{
vector<Wall*> walls;
for (int r = 0; r < 3; r++)
{
for (int c = 0; c < 3; c++)
{
if ((r + c) % 2 == 0)
{
Type type("blue", 10, "brick");
Point2D point(r, c);
Wall* w = new Wall(type, point);
walls.push_back(w);
}
else
{
Type type("red", 30, "steel");
Point2D point(r, c);
Wall* w = new Wall(type, point);
walls.push_back(w);
}
}
}
for (Wall* w : walls)
{
Type* tp = w->getType();
Point2D* pp = w->getPoint();
pp->printPoint();
tp->printType();
cout << " Type Address : " << tp << endl;
cout << "Point Address : " << pp << endl;
cout << endl;
}
return 0;
}
벽이 잘 생성되었고, Type과 Point의 주소가 모두 다른 것을 알 수 있다.
플라이웨이트 패턴 적용
플라이웨이트 패턴은 많은 수의 비슷한 객체를 만들 때 유용한 패턴이다.
위의 예시에서 훨씬 더 많이 벽을 만든다면, 상당히 많은 메모리가 필요하게 된다.
벽의 좌표는 모두 다르지만, 벽의 Type은 공유가 가능하다.
즉, 플라이웨이트 패턴으로 외부 정보(= x, y 좌표)와 내부 정보(색깔, 무게, 질감)를 구분하여 객체를 생성할 수 있다.
어떤 벽을 생성할 것인지는 Flyweight Factory가 결정한다.
Factory가 공유 가능한 자원이 있다면 해당 자원을 return하고 그렇지 않다면 새로 만든다.
벽에서 Type은 공유가 가능하기 때문에 포인터로 변경한다. (Wall → FlyweightWall)
class FlyweightWall
{
public:
FlyweightWall(Type* t, Point2D p) : type(t), point(p) {}
Type* getType() { return type; }
Point2D* getPoint() { return &point; }
private:
Type* type;
Point2D point;
};
FlyweightFactory는 FlyweightWall을 만든다.
이때 팩토리는 Type에 대한 cache(= map)를 가지고 있어서,
같은 색깔 + 무게 + 질감의 Type(= key)이 들어오면 해당 Type을 return한다.
새로 들어오는 Type이라면 해당 Type을 map에 저장하고 새롭게 만들어진 Type으로 벽을 만들면 된다.
class FlyweightFactory
{
public:
FlyweightWall* getWall(const string& color, int weight, const string& texture, int x, int y)
{
Point2D point(x, y);
string key = color + to_string(weight) + texture;
if (typeMap.find(key) != typeMap.end())
return new FlyweightWall(typeMap[key].get(), point);
else
{
shared_ptr<Type> type = make_shared<Type>(color, weight, texture);
typeMap[key] = type;
return new FlyweightWall(type.get(), point);
}
}
private:
map<string, shared_ptr<Type>> typeMap;
};
벽을 만들 때, 아래와 같이 모든 정보를 직접 넣도록 변경하였다.
FlyweightWall* w = flyweightFactory->getWall("blue", 10, "brick", r, c);
이제 코드를 실행해 보자.
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <map>
using namespace std;
class Type /* Shared Information */
{
public:
Type(const string& c, int w, const string& t) : color(c), weight(w), texture(t) {}
void printType() { cout << color << ", " << weight << "kg, " << texture << endl; }
private:
string color;
int weight;
string texture;
};
class Point2D /* Unique Information */
{
public:
Point2D(int _x, int _y) : x(_x), y(_y) {}
void printPoint() { cout << "Point : (" << x << ", " << y << ")" << endl; }
private:
int x;
int y;
};
class FlyweightWall
{
public:
FlyweightWall(Type* t, Point2D p) : type(t), point(p) {}
Type* getType() { return type; }
Point2D* getPoint() { return &point; }
private:
Type* type;
Point2D point;
};
class FlyweightFactory
{
public:
FlyweightWall* getWall(const string& color, int weight, const string& texture, int x, int y)
{
Point2D point(x, y);
string key = color + to_string(weight) + texture;
if (typeMap.find(key) != typeMap.end())
return new FlyweightWall(typeMap[key].get(), point);
else
{
shared_ptr<Type> type = make_shared<Type>(color, weight, texture);
typeMap[key] = type;
return new FlyweightWall(type.get(), point);
}
}
private:
map<string, shared_ptr<Type>> typeMap;
};
int main()
{
vector<FlyweightWall*> walls;
FlyweightFactory* flyweightFactory = new FlyweightFactory();
for (int r = 0; r < 3; r++)
{
for (int c = 0; c < 3; c++)
{
if ((r + c) % 2 == 0)
{
FlyweightWall* w = flyweightFactory->getWall("blue", 10, "brick", r, c);
walls.push_back(w);
}
else
{
FlyweightWall* w = flyweightFactory->getWall("red", 30, "steel", r, c);
walls.push_back(w);
}
}
}
for (FlyweightWall* w : walls)
{
Type* tp = w->getType();
Point2D* pp = w->getPoint();
pp->printPoint();
tp->printType();
cout << " Type Address : " << tp << endl;
cout << "Point Address : " << pp << endl;
cout << endl;
}
return 0;
}
좌표 조건에 따라 Type을 공유하는 것을 알 수 있다.
ClassDiagram.md
```mermaid
classDiagram
class FlyweightFactory {
Flyweight : Cache
getFlyweight()
}
class Flyweight {
shared
unique
}
FlyweightFactory o--> Flyweight
```