본문 바로가기
개발/Architecture & Design Pattern

C++ - 복합체, 컴포지트 패턴 (Composite Pattern)

by 피로물든딸기 2024. 2. 9.
반응형

C, C++ 전체 링크

Architecture & Design Pattern 전체 링크

 

참고

- 반복자, 이터레이터 패턴 (Iterator Pattern)

- 스마트 포인터 : unique_ptr

 

복합체, 컴포지트 패턴 (Composite Pattern) - 구조 패턴

- 객체들을 트리 구조로 구성하여 부분-전체 계층 구조(part-whole hierarchy) 표현하는 패턴

- 클라이언트에서 개별 객체와 다른 객체들로 구성된 복합 객체(composite)를 똑같은 방식으로 다룰 수 있다.

 

구현

- Component (구성 요소) : 인터페이스 또는 추상 클래스를 정의, 복합 객체와 개별 객체에 공통된 동작을 정의

- Leaf (원소) : Component를 구현, 단일 객체, 메서드를 구현

- Composite (복합체) : Component를 구현, Leaf 객체나 다른 Composite 객체들의 집합, 자식 요소 관리

- 클라이언트는 Component 인터페이스를 사용하여 Leaf나 Composite 객체들을 동일하게 다룬다.

 

장점

- OCP(개방-폐쇄 원칙), 코드를 수정하지 않고 새로운 종류의 개별 객체나 복합 객체를 추가하기 쉬움 (확장성)

- 다형성과 재귀 함수를 이용해 개별 객체와 복합 객체를 동일한 방식으로 처리 (유연성)

- 객체들의 계층 구조를 나타내는 데 유용, 복잡한 구조를 간결히 표현 (구조적 일관성)

 

단점

- 계층 구조가 복잡할수록 이해하고 유지보수가 어려울 수 있다.

- 너무 간단한 구조는 트리 구조인 복합체 패턴을 적용하는데 비용이 크다.

- 기능이 서로 다른 클래스들은 공통 인터페이스를 만들기 어렵기 때문에 컴포넌트 인터페이스를 일반화하기 어렵다.


트리 형태의 건물

 

이터레이터 패턴으로 건물을 관리했던 건물주를 조금 더 확장해 보자.

→ 각 건물에는 연관된 또 다른 작은 건물이 있다고 가정하자. (subApart, subSubApart, subOffice ....)

 

그리고 이런 계층 구조를 관리하기 위해 컴포지트 패턴을 사용해 보자.

 

클라이언트 Component 인터페이스를 이용해서 LeafComposite 객체를 다룰 수 있다.

ComponentLeafComposite 모두 적용되는 인터페이스추상 클래스가 된다.

 

BuildingComposite어떤 타입인지 (아파트, 오피스, 상가 등) 알 수 있는 정보가 있고,

BuildingComponent(← Leaf)를 더하거나 제거하고, 모든 Leaf를 출력하는 print를 구현한다.

class BuildingComposite : public BuildingComponent
{
public:
	BuildingComposite() = default;
	BuildingComposite(string type) : buildingType(type) {}
	void add(BuildingComponent* bc) override { buildingComponent.push_back(bc); }
	void remove(BuildingComponent* bc) override 
	{ 
		if (!buildingComponent.empty())
		{
			delete bc; 
			buildingComponent.remove(bc); 
		}
	}

	void print() const override
	{
		cout << "-----------------------------------------" << endl;
		cout << "Type : " << getType() << endl;
		for (const auto &bc : buildingComponent) bc->print();
		cout << "-----------------------------------------" << endl;
	}

	string getType() const override { return buildingType; }

private:
	string buildingType;
	list<BuildingComponent*> buildingComponent;
};

 

Leaf누가 몇 층에 사는지에 대한 정보와 Leaf를 출력하는 print를 구현한다.

즉, 복합 객체의 원소에 해당하는 정보와 메서드를 구현한다.

class BuildingLeaf : public BuildingComponent
{
public:
	BuildingLeaf() = default;
	BuildingLeaf(int f, string n) : floor(f), name(n) {}

	void print() const override 
	{ cout << "(" << getFloor() << ") [ " << getName() << " ]" << endl; }

	int getFloor() const override { return floor; }
	string getName() const override { return name; }

private:
	int floor;
	string name;
	list<BuildingComponent*> buildingComponent;
};

 

BuildingComponent는 BuildingComposite, BuildingLeaf에 적용되는 모든 메서드를 가진다.

Leaf나 Composite에 구현할 필요가 없는 메서드가 서로 존재하기 때문에 아래와 같이 exception으로 구현하였다.

class BuildingComponent
{
public:
	virtual ~BuildingComponent() = default;
	virtual void add(BuildingComponent* buildingComponent) 
	{ throw invalid_argument("add(): Unsupported Operation"); }
	virtual void remove(BuildingComponent* buildingComponent) 
	{ throw invalid_argument("remove(): Unsupported Operation"); }
	// virtual BuildingComponent* getChild(int i) const 
	// { throw invalid_argument("getChid(): Unsupported Operation"); }

	virtual int getFloor() const { throw invalid_argument("getFloor(): Unsupported Operation"); }
	virtual string getName() const { throw invalid_argument("getName(): Unsupported Operation"); }
	virtual string getType() const { throw invalid_argument("getType(): Unsupported Operation"); }
	virtual void print() const { throw invalid_argument("print(): Unsupported Operation"); }
};

 

건물주(클라이언트)는 최상의 건물 구성요소만 가지면 된다.

class Manager
{
public:
	Manager(BuildingComponent* bc) : allBuildings(move(bc)) { }
	void printBuilding() { allBuildings->print(); }

private:
	BuildingComponent* allBuildings;
};

 

전체 코드는 다음과 같다.

#include <iostream>
#include <string>
#include <vector>
#include <list>

using namespace std;

class BuildingComponent
{
public:
	virtual ~BuildingComponent() = default;
	virtual void add(BuildingComponent* buildingComponent) 
	{ throw invalid_argument("add(): Unsupported Operation"); }
	virtual void remove(BuildingComponent* buildingComponent) 
	{ throw invalid_argument("remove(): Unsupported Operation"); }
	//  virtual BuildingComponent* getChild(int i) const 
	// { throw invalid_argument("getChid(): Unsupported Operation"); }

	virtual int getFloor() const { throw invalid_argument("getFloor():Unsupported Operation"); }
	virtual string getName() const { throw invalid_argument("getName():Unsupported Operation"); }
	virtual string getType() const { throw invalid_argument("getType():Unsupported Operation"); }
	virtual void print() const { throw invalid_argument("print():Unsupported Operation"); }
};

class BuildingComposite : public BuildingComponent
{
public:
	BuildingComposite() = default;
	BuildingComposite(string type) : buildingType(type) {}
	void add(BuildingComponent* bc) override { buildingComponent.push_back(bc); }
	void remove(BuildingComponent* bc) override
	{
		if (!buildingComponent.empty())
		{
			delete bc;
			buildingComponent.remove(bc);
		}
	}

	void print() const override
	{
		cout << "-----------------------------------------" << endl;
		cout << "Type : " << getType() << endl;
		for (const auto &bc : buildingComponent) bc->print();
		cout << "-----------------------------------------" << endl;
	}

	string getType() const override { return buildingType; }

private:
	string buildingType;
	list<BuildingComponent*> buildingComponent;
};

class BuildingLeaf : public BuildingComponent
{
public:
	BuildingLeaf() = default;
	BuildingLeaf(int f, string n) : floor(f), name(n) {}

	void print() const override 
	{ cout << "(" << getFloor() << ") [ " << getName() << " ]" << endl; }

	int getFloor() const override { return floor; }
	string getName() const override { return name; }

private:
	int floor;
	string name;
	list<BuildingComponent*> buildingComponent;
};

/* ----------------- Manager ------------------ */

class Manager
{
public:
	Manager(BuildingComponent* bc) : allBuildings(move(bc)) { }
	void printBuilding() { allBuildings->print(); }

private:
	BuildingComponent* allBuildings;
};

int main(void)
{
	BuildingComposite* apart = new BuildingComposite("Apart");
	BuildingComposite* office = new BuildingComposite("Office");
	BuildingComposite* store = new BuildingComposite("Store");

	BuildingComposite* subApart = new BuildingComposite("Sub Apart");
	BuildingComposite* subOffice = new BuildingComposite("Sub Office");

	BuildingComposite* subSubApart1 = new BuildingComposite("Sub Sub Apart 1");
	BuildingComposite* subSubApart2 = new BuildingComposite("Sub Sub Apart 2");

	BuildingComposite* allBuildings = new BuildingComposite("All Building");

	allBuildings->add(apart);
	allBuildings->add(office);
	allBuildings->add(store);

	// ------------------------------------------------------------------ //
	vector<BuildingLeaf*> apartItems{
		new BuildingLeaf(1, "blood"),
		new BuildingLeaf(5, "straw"),
		new BuildingLeaf(3, "berry"),
	};

	for (auto &item : apartItems) apart->add(item);

	vector<BuildingLeaf*> subApartItems{
		new BuildingLeaf(10, "sub apart 1"),
		new BuildingLeaf(11, "sub apart 2"),
		new BuildingLeaf(13, "sub apart 3"),
	};

	for (auto &item : subApartItems) subApart->add(item);

	apart->add(subApart);

	vector<BuildingLeaf*> subSubApartItems1{
		new BuildingLeaf(131, "sub sub apart 1"),
		new BuildingLeaf(185, "sub sub apart 2"),
		new BuildingLeaf(172, "sub sub apart 3"),
	};

	vector<BuildingLeaf*> subSubApartItems2{
		new BuildingLeaf(231, "sub sub apart 4"),
		new BuildingLeaf(285, "sub sub apart 5"),
	};

	for (auto &item : subSubApartItems1) subSubApart1->add(item);
	for (auto &item : subSubApartItems2) subSubApart2->add(item);

	subApart->add(subSubApart1);
	subApart->add(subSubApart2);

	// ------------------------------------------------------------------ //
	vector<BuildingLeaf*> officeItems{
		new BuildingLeaf(1, "LG"),
		new BuildingLeaf(5, "NC"),
		new BuildingLeaf(4, "SK"),
		new BuildingLeaf(3, "HYUNDAI"),
	};

	for (auto &item : officeItems) office->add(item);

	vector<BuildingLeaf*> subOfficeItems{
		new BuildingLeaf(30, "sub office 1"),
		new BuildingLeaf(31, "sub office 2"),
		new BuildingLeaf(32, "sub office 3"),
	};

	for (auto &item : subOfficeItems) subOffice->add(item);

	office->add(subOffice);
	// ------------------------------------------------------------------ //
	vector<BuildingLeaf*> storeItems{
		new BuildingLeaf(7, "steak"),
		new BuildingLeaf(3, "chicken"),
		new BuildingLeaf(1, "fork"),
	};

	for (auto &item : storeItems) store->add(item);
	// ------------------------------------------------------------------ //
	Manager* manager = new Manager(allBuildings);

	manager->printBuilding();

	for (auto &item : apartItems) delete item;
	for (auto &item : subApartItems) delete item;
	for (auto &item : subSubApartItems1) delete item;
	for (auto &item : subSubApartItems2) delete item;
	for (auto &item : officeItems) delete item;
	for (auto &item : subOfficeItems) delete item;
	for (auto &item : storeItems) delete item;

	return 0;
}

 

현재 Component 클래스는 Composite와 Leaf의 기능이 모두 들어가 있어서 안전성이 떨어진다.

클라이언트가 Composite이나 Leaf가 사용해서는 안 될 메서드를 사용하려고 할 수 있기 때문이다.


이터레이터 패턴 적용

 

이제 컴포지트 패턴 내에서 이터레이터 패턴을 적용해 보자.

template <typename T>
class Iterator
{
public:
	virtual ~Iterator() = default;
	virtual T* next() = 0;
	virtual bool hasNext() = 0;
	virtual void remove() = 0;
};

 

Component에는 createIterator 메서드를 추가한다.

class BuildingComponent
{
public:
	virtual ~BuildingComponent() = default;
	virtual Iterator<BuildingComponent>* createIterator() = 0;
    
    ...
};

 

이제 CompositeLeaf에도 createIterator를 구현해야 한다.

 

먼저 Leaf에 대해 반복 작업을 하는 CompositeIterator를 만들자.

생성자에는 반복 작업을 할 최상위 객체가 전달된다.

그리고 iterator next 메서드재귀적으로 호출해서 모든 원소에 접근한다.

class CompositeIterator : public Iterator<BuildingComponent>
{
public:
	CompositeIterator(Iterator<BuildingComponent>* iterator) { stk.push(iterator); }
	BuildingComponent* next() override
	{
		if (!hasNext()) return nullptr;

		Iterator<BuildingComponent>* iterator = stk.top();
		BuildingComponent* component = iterator->next(); // recursive
		stk.push(component->createIterator());

		return component;
	}

	bool hasNext() override
	{
		if (stk.empty()) return false;

		Iterator<BuildingComponent>* iterator = stk.top();
		if (!iterator->hasNext())
		{
			stk.pop();
			return hasNext(); // recursive
		}

		return true;
	}

	void remove() override 
	{ throw invalid_argument("CompositeIterator::remove(): Unsupported Operation"); }

private:
	stack<Iterator<BuildingComponent>*> stk;
};

 

BuildingIterator는 다음과 같다.

class BuildingIterator : public Iterator<BuildingComponent>
{
public:
	BuildingIterator(list<BuildingComponent*> &bc) : components(bc), iter(components.begin()) {}
	BuildingComponent* next() override { return *iter++; }
	bool hasNext() override { return iter != components.end(); }
	void remove() override 
	{ throw invalid_argument("BuildingIterator::remove(): Unsupported Operation"); }

private:
	list<BuildingComponent*> &components;
	list<BuildingComponent*>::iterator iter;
};

 

BuildingComponentBuildingIterator를 만들고 CompositeIterator를 생성한다.

class BuildingComposite : public BuildingComponent
{
public:
	BuildingComposite() = default;
	BuildingComposite(string type) : buildingType(type) {}

	Iterator<BuildingComponent>* createIterator() override
	{
		if (iterator == nullptr)
		{
			buildingIterator = make_unique<BuildingIterator>(buildingComponent);
			iterator = make_unique<CompositeIterator>(buildingIterator.get());
		}

		return iterator.get();
	}

	...

private:
	string buildingType;
	list<BuildingComponent*> buildingComponent;
	unique_ptr<BuildingIterator> buildingIterator = nullptr;
	unique_ptr<Iterator<BuildingComponent>> iterator = nullptr;
};

 

BuildingLeaf는 반복 작업을 할 대상이 없으므로, createIterator 메서드를 구현하기 애매해진다.

그냥 null을 return해도 되지만, 해당 메서드를 호출하는 클라이언트가 null check를 할 필요가 있다.

따라서 hasNext()가 호출될 때, falsereturn하는 널 이터레이터를 선언해서 아무 일도 하지 않도록 처리한다.

class NullIterator : public Iterator<BuildingComponent>
{
public:
	BuildingComponent* next() override { return nullptr; }
	bool hasNext() override { return false; }
	void remove() override 
	{ throw invalid_argument("NullIterator::remove(): Unsupported Operation"); }
};

class BuildingLeaf : public BuildingComponent
{
public:
	BuildingLeaf() = default;
	BuildingLeaf(int f, string n) : floor(f), name(n) {}

	Iterator<BuildingComponent>* createIterator() override { return nullIterator.get(); }

private:
	...
	static unique_ptr<NullIterator> nullIterator;
};

unique_ptr<NullIterator> BuildingLeaf::nullIterator = make_unique<NullIterator>();

 

이제 Iterator를 이용하여 홀수 층 건물만 출력하도록 해보자.

getFloor()가 구현되지 않은 BuildingCompositecatch에 의해 아무 일도 하지 않는다.

class Manager
{
public:
	Manager(BuildingComponent* bc) : allBuildings(bc) { }
	void printBuilding() { allBuildings->print(); }
	void printBuildingIter()
	{
		Iterator<BuildingComponent>* iterator = allBuildings->createIterator();
        
		cout << "Print with Iterator" << endl;
		while (iterator->hasNext())
		{
			BuildingComponent* bc = iterator->next();

			try 
			{
				if (bc->getFloor() % 2 == 1) 
					bc->print();
			}
			catch (invalid_argument e) {}			
		}
	}

private:
	BuildingComponent* allBuildings;
};

 

홀수 층만 출력되는 것을 확인해 보자.

 

전체 코드는 다음과 같다.

#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <list>
#include <stack>

using namespace std;

template <typename T>
class Iterator
{
public:
	virtual ~Iterator() = default;
	virtual T* next() = 0;
	virtual bool hasNext() = 0;
	virtual void remove() = 0;
};

class BuildingComponent
{
public:
	virtual ~BuildingComponent() = default;
	virtual Iterator<BuildingComponent>* createIterator() = 0;

	virtual void add(BuildingComponent* buildingComponent) 
	{ throw invalid_argument("add(): Unsupported Operation"); }
	virtual void remove(BuildingComponent* buildingComponent) 
	{ throw invalid_argument("remove(): Unsupported Operation"); }
	// virtual BuildingComponent* getChild(int i) const 
	// { throw invalid_argument("getChid(): Unsupported Operation"); }

	virtual int getFloor() const { throw invalid_argument("getFloor():Unsupported Operation"); }
	virtual string getName() const { throw invalid_argument("getName():Unsupported Operation"); }
	virtual string getType() const { throw invalid_argument("getType():Unsupported Operation"); }
	virtual void print() const { throw invalid_argument("print():Unsupported Operation"); }
};

class CompositeIterator : public Iterator<BuildingComponent>
{
public:
	CompositeIterator(Iterator<BuildingComponent>* iterator) { stk.push(iterator); }
	BuildingComponent* next() override
	{
		if (!hasNext()) return nullptr;

		Iterator<BuildingComponent>* iterator = stk.top();
		BuildingComponent* component = iterator->next(); // recursive
		stk.push(component->createIterator());

		return component;
	}

	bool hasNext() override
	{
		if (stk.empty()) return false;

		Iterator<BuildingComponent>* iterator = stk.top();
		if (!iterator->hasNext())
		{
			stk.pop();
			return hasNext(); // recursive
		}

		return true;
	}

	void remove() override 
	{ throw invalid_argument("CompositeIterator::remove(): Unsupported Operation"); }

private:
	stack<Iterator<BuildingComponent>*> stk;
};

class BuildingIterator : public Iterator<BuildingComponent>
{
public:
	BuildingIterator(list<BuildingComponent*> &bc) : components(bc), iter(components.begin()) {}
	BuildingComponent* next() override { return *iter++; }
	bool hasNext() override { return iter != components.end(); }
	void remove() override 
	{ throw invalid_argument("BuildingIterator::remove(): Unsupported Operation"); }

private:
	list<BuildingComponent*> &components;
	list<BuildingComponent*>::iterator iter;
};

class NullIterator : public Iterator<BuildingComponent>
{
public:
	BuildingComponent* next() override { return nullptr; }
	bool hasNext() override { return false; }
	void remove() override 
	{ throw invalid_argument("NullIterator::remove(): Unsupported Operation"); }
};

class BuildingComposite : public BuildingComponent
{
public:
	BuildingComposite() = default;
	BuildingComposite(string type) : buildingType(type) {}

	Iterator<BuildingComponent>* createIterator() override
	{
		if (iterator == nullptr)
		{
			buildingIterator = make_unique<BuildingIterator>(buildingComponent);
			iterator = make_unique<CompositeIterator>(buildingIterator.get());
		}

		return iterator.get();
	}

	void add(BuildingComponent* bc) override { buildingComponent.push_back(bc); }
	void remove(BuildingComponent* bc) override { buildingComponent.remove(bc); }

	void print() const override
	{
		cout << "-----------------------------------------" << endl;
		cout << "Type : " << getType() << endl;
		for (const auto &bc : buildingComponent) bc->print();
		cout << "-----------------------------------------" << endl;
	}

	string getType() const override { return buildingType; }

private:
	string buildingType;
	list<BuildingComponent*> buildingComponent;
	unique_ptr<BuildingIterator> buildingIterator = nullptr;
	unique_ptr<Iterator<BuildingComponent>> iterator = nullptr;
};

class BuildingLeaf : public BuildingComponent
{
public:
	BuildingLeaf() = default;
	BuildingLeaf(int f, string n) : floor(f), name(n) {}

	Iterator<BuildingComponent>* createIterator() override { return nullIterator.get(); }
	void print() const override 
	{ cout << "(" << getFloor() << ") [ " << getName() << " ]" << endl; }

	int getFloor() const override { return floor; }
	string getName() const override { return name; }

private:
	int floor;
	string name;
	list<BuildingComponent*> buildingComponent;
	static unique_ptr<NullIterator> nullIterator;
};

unique_ptr<NullIterator> BuildingLeaf::nullIterator = make_unique<NullIterator>();

/* ----------------- Manager ------------------ */

class Manager
{
public:
	Manager(BuildingComponent* bc) : allBuildings(bc) { }
	void printBuilding() { allBuildings->print(); }
	void printBuildingIter()
	{
		Iterator<BuildingComponent>* iterator = allBuildings->createIterator();

		cout << "Print with Iterator" << endl;
		while (iterator->hasNext())
		{
			BuildingComponent* bc = iterator->next();

			try
			{
				if (bc->getFloor() % 2 == 1)
					bc->print();
			}
			catch (invalid_argument e) {}
		}
	}

private:
	BuildingComponent* allBuildings;
};

int main(void)
{
	BuildingComposite* apart = new BuildingComposite("Apart");
	BuildingComposite* office = new BuildingComposite("Office");
	BuildingComposite* store = new BuildingComposite("Store");

	BuildingComposite* subApart = new BuildingComposite("Sub Apart");
	BuildingComposite* subOffice = new BuildingComposite("Sub Office");

	BuildingComposite* subSubApart1 = new BuildingComposite("Sub Sub Apart 1");
	BuildingComposite* subSubApart2 = new BuildingComposite("Sub Sub Apart 2");

	BuildingComposite* allBuildings = new BuildingComposite("All Building");

	allBuildings->add(apart);
	allBuildings->add(office);
	allBuildings->add(store);

	// ------------------------------------------------------------------ //
	vector<BuildingLeaf*> apartItems{
		new BuildingLeaf(1, "blood"),
		new BuildingLeaf(5, "straw"),
		new BuildingLeaf(3, "berry"),
	};

	for (auto &item : apartItems) apart->add(item);

	vector<BuildingLeaf*> subApartItems{
		new BuildingLeaf(10, "sub apart 1"),
		new BuildingLeaf(11, "sub apart 2"),
		new BuildingLeaf(13, "sub apart 3"),
	};

	for (auto &item : subApartItems) subApart->add(item);

	apart->add(subApart);

	vector<BuildingLeaf*> subSubApartItems1{
		new BuildingLeaf(131, "sub sub apart 1"),
		new BuildingLeaf(185, "sub sub apart 2"),
		new BuildingLeaf(172, "sub sub apart 3"),
	};

	vector<BuildingLeaf*> subSubApartItems2{
		new BuildingLeaf(231, "sub sub apart 4"),
		new BuildingLeaf(285, "sub sub apart 5"),
	};

	for (auto &item : subSubApartItems1) subSubApart1->add(item);
	for (auto &item : subSubApartItems2) subSubApart2->add(item);

	subApart->add(subSubApart1);
	subApart->add(subSubApart2);

	// ------------------------------------------------------------------ //
	vector<BuildingLeaf*> officeItems{
		new BuildingLeaf(1, "LG"),
		new BuildingLeaf(5, "NC"),
		new BuildingLeaf(4, "SK"),
		new BuildingLeaf(3, "HYUNDAI"),
	};

	for (auto &item : officeItems) office->add(item);

	vector<BuildingLeaf*> subOfficeItems{
		new BuildingLeaf(30, "sub office 1"),
		new BuildingLeaf(31, "sub office 2"),
		new BuildingLeaf(32, "sub office 3"),
	};

	for (auto &item : subOfficeItems) subOffice->add(item);

	office->add(subOffice);
	// ------------------------------------------------------------------ //
	vector<BuildingLeaf*> storeItems{
		new BuildingLeaf(7, "steak"),
		new BuildingLeaf(3, "chicken"),
		new BuildingLeaf(1, "fork"),
	};

	for (auto &item : storeItems) store->add(item);
	// ------------------------------------------------------------------ //
	Manager* manager = new Manager(allBuildings);

	manager->printBuildingIter();

	for (auto &item : apartItems) delete item;
	for (auto &item : subApartItems) delete item;
	for (auto &item : subSubApartItems1) delete item;
	for (auto &item : subSubApartItems2) delete item;
	for (auto &item : officeItems) delete item;
	for (auto &item : subOfficeItems) delete item;
	for (auto &item : storeItems) delete item;

	return 0;
}
반응형

댓글