개발/Architecture & Design Pattern

C++ - 플라이웨이트 패턴 (Flyweight Pattern)

피로물든딸기 2024. 3. 1. 20:11
반응형

C, C++ 전체 링크

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;
};

 

벽 클래스는 다음과 같이 정의할 수 있다.

벽을 구성하는 TypePoint2D메모리를 확인하기 위해 getTypegetPoint도 추가하였다.

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;
};

 

FlyweightFactoryFlyweightWall을 만든다.

이때 팩토리는 Type에 대한 cache(= map)를 가지고 있어서,

같은 색깔 + 무게 + 질감의 Type(= key)이 들어오면 해당 Typereturn한다.

새로 들어오는 Type이라면 해당 Typemap에 저장하고 새롭게 만들어진 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
```
반응형