의도

  • 공유를 통해 많은 수의 소립 객체들을 효과적으로 지원함

 

언제 쓰는가 ?

  • 응용프로그램이 대량의 객체를 사용해야 할 때
  • 객체의 수가 너무 많아져 저장 비용이 너무 높아질 때
  • 대부분의 객체 상태를 부가적인 것으로 만들 수 있을 때
  • 부가적인 속성들을 제거한 후 객체들을 조사해 보니 객체의 많은 묶음이 비교적 적은 수의 공유된 객체로 대체될 수 있을 때. 현재 서로 다른 객체로 간주한 이유는 이들 부가적인 속성 때문이었지 본질이 달랐던 것은 아닐 때
  • 응용프로그램이 객체의 정체성에 의존하지 않을 때. 플라이급 객체들은 공유될 수 있음을 의미하는데, 식별자가 있다는 것은 서로 다른 객체로 구별해야 한다는 의미이므로 플라이급 객체를 사용할 수 없음

 

구조

GoF의 디자인 패턴 중 P.269
GoF의 디자인 패턴 중 P.269

  • Flyweight : Flyweight가 받아들일 수 있고, 부가적 상태에서 동작해야하는 인터페이스를 선언함.
  • ConcreteFlyweight : Flyweight 인터페이스를 구현하고 내부적으로 갖고 있어야 하는 본질적 상태에 대한 저장소를 정의. ConcreteFlyweight 객체는 공유할 수 있는 것이어야 함. 그러므로 관리하는 어떤 상태라도 본질적인 것이어야 함.
  • UnsharedConcreteFlyweight : 모든 플라이급 서브클래스들이 공유될 필요는 없음. Flyweight 인터페이스는 공유를 가능하게 하지만, 그것을 강요해서는 안 됨. UnsharedConcreteFlyweight 객체가 ConreteFlyweight 객체를 자신의 자식으로 갖는 것은 흔한 일임.
  • FlyweightFactory : 플라이급 객체를 생성하고 관리하며, 플라이급 객체가 제대로 공유되도록 보장함. 사용자가 플라이급 객체를 요청하면 FlyweightFactory 객체는 이미 존재하는 인스턴스를 제공하거나 만약 존재하지 않는다면 새로 생성함.
  • Client : 플라이급 객체에 대한 참조자를 관리하며 플라이급 객체의 부가적 상태를 저장함.

 

협력 방법

  • 플라이급 객체가 기능을 수행하는 데 필요한 상태가 본질적인 것인지 부가적인 것인지를 구분해야 함. 본질적인 상태는 ConcreteFlyweight에 저장, 부가적인 상태는 사용자가 저장하거나, 연산되어야 하는 다른 상태로 관리해야 함. 사용자는 연산을 호출할 때 자신에게만 필요한 부가적인 상태를 플라이급 객체에 매개변수로 전달함.
  • 사용자는 ConcreteFlyweight의 인스턴스를 직접 만들 수 없음. 사용자는 ConcreteFlyweight 객체를 FlyweightFactory 객체에서 얻어야 함. 이렇게 해야 플라이급 객체가 공유될 수 있음.

 

결과

  • 공유해야 하는 인스턴스의 전체 수를 줄일 수 있음.
  • 객체별 본질적 상태의 양을 줄일 수 있음.
  • 부가적인 상태는 연산되거나 저장될 수 있음.

 

구현 / 고려사항

  1. 부가적 상태를 제외함
    1. 부가적 상태와 본질적 상태를 구분하는 것이 패턴의 핵심임. 저장소를 아낄 수 있음.
  2. 공유할 객체를 관리함
    1. 사용자가 직접 인스턴스를 만들게 하면 안됨. FlyweightFactory를 통해서 사용자가 플라이급 객체를 찾아내게 해야 함. FlyweightFactory는 연관 저장소를 써서 사용자 요청 시 없으면 새로 생성 후 반환, 있으면 그대로 반환하면 됨.
    2. 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

+ Recent posts