Skip to main content

[Design Pattern] 구조 패턴 7. 플라이웨이트 (Flyweight)




플라이웨이트 패턴 #

플라이웨이트

  • 공유를 통해 많은 수의 잘게 쪼개진 객체들을 효과적으로 사용한다.
  • 이렇게 함으로써 메모리 사용량을 줄일 수 있다.

게임에서의 예시 #

  • 맵에 있는 수천 그루의 나무를 렌더링해야 한다.
  • 나무마다 필요한 데이터는 다음과 같다.
    • 메시
    • 나무껍질 텍스처
    • 잎사귀 텍스처
    • 위치 값
    • 크기
    • 두께
  • 모든 나무는 메시와 나무껍질, 잎사귀 텍스처가 동일할 것이다.
    • 따라서 이런 것들은 메모리에 하나의 인스턴스만 올리고 공유하면 좋겠다.
// 공유 데이터 
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



References #