[Design Pattern] 구조 패턴 7. 플라이웨이트 (Flyweight)
Table of Contents
플라이웨이트 패턴 #
- 공유를 통해 많은 수의 잘게 쪼개진 객체들을 효과적으로 사용한다.
- 이렇게 함으로써 메모리 사용량을 줄일 수 있다.
게임에서의 예시 #
- 맵에 있는 수천 그루의 나무를 렌더링해야 한다.
- 나무마다 필요한 데이터는 다음과 같다.
- 메시
- 나무껍질 텍스처
- 잎사귀 텍스처
- 위치 값
- 크기
- 두께
- 모든 나무는 메시와 나무껍질, 잎사귀 텍스처가 동일할 것이다.
- 따라서 이런 것들은 메모리에 하나의 인스턴스만 올리고 공유하면 좋겠다.
// 공유 데이터
class TreeModel
{
private:
Mesh mesh;
Texture bark;
Texture leaves;
};
class Tree
{
private:
// 공유 데이터를 참조만 한다.
TreeModel* model;
Vector position;
double height;
double thickness;
};
- 이제 지형정보를 플라이웨이트 패턴을 이용해서 표현해보자.
- 지형정보는 타일로 나타낸다. 하나의 타일은 다음과 같은 정보를 가지고 있다.
- 플레이어가 이동하는 데 드는 비용 값
- 건너갈 수 있는 곳인지 여부
- 렌더링할 때 사용할 텍스처
enum Terrain { GRASS, HILL, RIVER };
class World
{
private:
// 모든 타일은
Terrain* tiles[WIDTH][HEIGHT];
// 이 세 가지 터레인 중에 하나다.
// 그래서 tiles는 터레인의 포인터로 전체 맵을 표현할 수 있다. (공유된다)
Terrain grassTerrain;
Terrain hillTerrain;
Terrain riverTerrain;
public:
// 터레인 인스턴스를 만든다.
World() : grassTerrain(1, false, GRASS_TEXTURE),
hillTerrain(3, false, HILL_TEXTURE),
riverTerrain(2, true, WATER_TEXTURE) {}
void generateTerrain()
{
for (int x = 0; x < WIDTH; x++)
{
for (int y = 0; y < HEIGHT; y++)
{
if (random(10) == 0) tiles[x][y] = &hillTerrain;
else tiles[x][y] = &grassTerrain;
}
}
int x = random(WIDTH);
for (int y = 0; y < HEIGHT; y++)
tiles[x][y] = &riverTerrain;
}
const Terrain& getTile(int x, int y) const
{
return *tiles[x][y];
}
};
class Terrain
{
private:
int movementCost;
bool isWater;
Texture texture;
public:
Terrain(int _movementCost, bool _isWater, Texture _texture)
: movementCost(_movementCost), isWater(_isWater), texture(_texture) {}
int getMovementCost(int x, int y);
int isWater(int x, int y);
const Texture& getTexture() const;
};
개념적인 예시 #
public class Flyweight
{
// 플라이웨이트는 공유되는 상태를 가지고 있다.
private Car _sharedState;
public Flyweight(Car car)
{
this._sharedState = car;
}
public void Operation(Car uniqueState)
{
string s = JsonConvert.SerializeObject(this._sharedState);
string u = JsonConvert.SerializeObject(uniqueState);
Console.WriteLine($"Flyweight: Displaying shared {s} and unique {u} state.");
}
}
// 플라이웨이트를 관리하는 플라이웨이트 팩토리
public class FlyweightFactory
{
private List<Tuple<Flyweight, string>> flyweights = new List<Tuple<Flyweight, string>>();
public FlyweightFactory(params Car[] args)
{
foreach (var elem in args)
{
flyweights.Add(new Tuple<Flyweight, string>(new Flyweight(elem), this.getKey(elem)));
}
}
// 플라이웨이트의 스트링 해시를 가져온다.
private string getKey(Car key)
{
List<string> elements = new List<string>();
elements.Add(key.Model);
elements.Add(key.Color);
elements.Add(key.Company);
if (key.Owner != null && key.Number != null)
{
elements.Add(key.Number);
elements.Add(key.Owner);
}
elements.Sort();
return string.Join("_", elements);
}
public Flyweight GetFlyweight(Car sharedState)
{
string key = this.getKey(sharedState);
if (flyweights.Where(t => t.Item2 == key).Count() == 0)
{
Console.WriteLine("FlyweightFactory: Can't find a flyweight, creating new one.");
// 해당 플라이웨이트가 없으므로 새로운 것을 추가한다.
this.flyweights.Add(new Tuple<Flyweight, string>(new Flyweight(sharedState), key));
}
else
{
// 해당 플라이웨이트가 있으면 기존의 것을 반환한다.
Console.WriteLine("FlyweightFactory: Reusing existing flyweight.");
}
return this.flyweights.Where(t => t.Item2 == key).FirstOrDefault().Item1;
}
public void ListFlyweights()
{
var count = flyweights.Count;
Console.WriteLine($"\nFlyweightFactory: I have {count} flyweights:");
foreach (var flyweight in flyweights)
{
Console.WriteLine(flyweight.Item2);
}
}
}
public class Car
{
public string Owner { get; set; }
public string Number { get; set; }
public string Company { get; set; }
public string Model { get; set; }
public string Color { get; set; }
}
class Program
{
static void Main(string[] args)
{
var factory = new FlyweightFactory(
new Car { Company = "Chevrolet", Model = "Camaro2018", Color = "pink" },
new Car { Company = "Mercedes Benz", Model = "C300", Color = "black" },
new Car { Company = "Mercedes Benz", Model = "C500", Color = "red" },
new Car { Company = "BMW", Model = "M5", Color = "red" },
new Car { Company = "BMW", Model = "X6", Color = "white" }
);
factory.ListFlyweights();
AddCarToPoliceDatabase(factory, new Car {
Number = "CL234IR",
Owner = "James Doe",
Company = "BMW",
Model = "M5",
Color = "red"
});
AddCarToPoliceDatabase(factory, new Car {
Number = "CL234IR",
Owner = "James Doe",
Company = "BMW",
Model = "X1",
Color = "red"
});
factory.ListFlyweights();
}
// 새로운 Car를 추가한다.
public static void AddCarToPoliceDatabase(FlyweightFactory factory, Car car)
{
Console.WriteLine("\nClient: Adding a car to database.");
var flyweight = factory.GetFlyweight(new Car {
Color = car.Color,
Model = car.Model,
Company = car.Company
});
flyweight.Operation(car);
}
}
- 출력 결과
FlyweightFactory: I have 5 flyweights:
Camaro2018_Chevrolet_pink
black_C300_Mercedes Benz
C500_Mercedes Benz_red
BMW_M5_red
BMW_white_X6
Client: Adding a car to database.
FlyweightFactory: Reusing existing flyweight.
Flyweight: Displaying shared {"Owner":null,"Number":null,"Company":"BMW","Model":"M5","Color":"red"} and unique {"Owner":"James Doe","Number":"CL234IR","Company":"BMW","Model":"M5","Color":"red"} state.
Client: Adding a car to database.
FlyweightFactory: Can't find a flyweight, creating new one.
Flyweight: Displaying shared {"Owner":null,"Number":null,"Company":"BMW","Model":"X1","Color":"red"} and unique {"Owner":"James Doe","Number":"CL234IR","Company":"BMW","Model":"X1","Color":"red"} state.
FlyweightFactory: I have 6 flyweights:
Camaro2018_Chevrolet_pink
black_C300_Mercedes Benz
C500_Mercedes Benz_red
BMW_M5_red
BMW_white_X6
BMW_red_X1