복합체 패턴(Composite Pattern)
- 복합체 패턴은 객체 간의 계층적 구조화를 통해 객체를 확장하는 패턴입니다.
- 계층적 구조는 재귀(Recursive)와 트리(Tree) 구조를 생각하면 좋습니다.
- 일반적인 복합 객체로부터 복합체 패턴으로 확장해보겠습니다.
복합 객체
- 복합 객체는 한 객체가 다른 객체를 포함하고 있는 관계를 의미합니다.
- 대표적인 복합 객체로 컴퓨터가 있습니다.
- 컴퓨터는 다양한 객체(모니터, 메모리, 저장장치 등)를 포함하고 있습니다.
class Computer
{
public:
void setMonitor(std::shared_ptr<Monitor> monitor)
{
m_monitor = monitor;
}
void setDisk(std::shared_ptr<Disk> disk)
{
m_disk = disk;
}
void setMemory(std::shared_ptr<Memory> memory)
{
m_memory = memory;
}
std::shared_ptr<Monitor> m_monitor;
std::shared_ptr<Disk> m_disk;
std::shared_ptr<Memory> m_memory;
std::string m_name = "Computer Composite";
};
Computer
는Monitor
,Disk
,Memory
를 포함하고 있는 복합 객체입니다.- 복합 객체는 복합 객체를 포함하는것이 가능합니다.
Monitor
예제를 살펴보겠습다.
class Monitor
{
public:
void addMonitor(std::shared_ptr<Monitor32> monitor)
{
m_screen.push_back(monitor);
}
void show()
{
for (const auto& item : m_screen)
{
std::cout << item->m_name << std::endl;
}
}
std::string m_name = "Monitor";
std::vector<std::shared_ptr<Monitor32>> m_screen;
};
class Monitor32
{
public:
std::string m_name = "32-inch Monitor";
};
Monitor
는 32인치 모니터Monitor32
를 포함하고 있습니다.Disk
와Memory
도 확인해보겠습니다.
class Disk
{
public:
std::string m_name = "Disk";
};
class Memory
{
public:
std::string m_name = "Memory";
};
- 이러한 복합 객체를 사용하는 방법은 다음과 같습니다.
int main(const int argc, const char* argv[])
{
std::shared_ptr<Computer> computer = std::make_shared<Computer>();
computer->setMonitor(std::make_shared<Monitor>());
computer->m_monitor->addMonitor(std::make_shared<Monitor32>());
computer->m_monitor->addMonitor(std::make_shared<Monitor32>());
computer->setDisk(std::make_shared<Disk>());
computer->setMemory(std::make_shared<Memory>());
std::cout << computer->m_name << std::endl;
std::cout << computer->m_monitor->m_name << std::endl;
computer->m_monitor->show();
std::cout << computer->m_disk->m_name << std::endl;
std::cout << computer->m_memory->m_name << std::endl;
return 0;
}
- 출력 결과
Computer Composite
Monitor
32-inch Monitor
32-inch Monitor
Disk
Memory
- 이렇게 복합 객체는 다른 객체를 포함할 수 있습니다.
복합체 패턴의 구조
복합체 패턴은 복합 객체의 특징을 활용한 패턴입니다.
복합체 패턴은 재귀적 결합을 통해 하나의 객체가 다수의 연결 객체를 가질 수 있습니다.
복합체 패턴은 4개의 구성 요소를 갖고 있습니다.
- 컴포넌트(Component)
- Composite와 Leaf 클래스를 모두 관리하기 위한 인터페이스 클래스입니다.
- 컴포지트(Composite)
- Compisite 클래스는 다른 객체를 포함할 수 있는 클래스입니다.
- 리프(Leaf)
- Leaf 클래스는 마지막 노드로 다른 객체를 포함할 수 없는 클래스입니다.
- 클라이언트(Client)
- 컴포넌트(Component)
또 하나 특이한 점은 복합체 패턴은 객체지향의 원칙인 **단일 책임 원칙(SRP)**을 만족하지 않습니다.
다른 객체를 포함하면서 자연스럽게 두 개 이상의 책임을 갖게 됩니다.
복합체 패턴: 컴포넌트(Component) 구조
- 컴포넌트는
Composite
,Leaf
를 관리할 수 있는 인터페이스 클래스입니다.
class Component
{
public:
void setName(std::string name)
{
m_name = name;
}
std::string getName()
{
return m_name;
}
virtual void print(int idx) = 0;
private:
std::string m_name;
};
복합체 패턴: 컴포지트(Composite) 구조
- 다른 객체를 포함할 수 있는
Composite
입니다. Leaf
와는 다르게 다른 객체를 추가하는 메서드가 있습니다.
class Composite :
public Component
{
public:
Composite(std::string name)
{
setName(name);
}
void addNode(std::shared_ptr<Component> component)
{
m_comp.push_back(component);
}
void print(int idx = 0) override final
{
for (int i = 0; i < idx; i++)
{
std::cout << "\t";
}
std::cout << "Directory : " << getName() << std::endl;
for (const auto& item : m_comp)
{
item->print(idx + 1);
}
}
private:
std::vector<std::shared_ptr<Component>> m_comp;
};
복합체 패턴: 리프(Leaf) 구조
- 복합체 패턴에서 다른 객체를 포함하지 않고 제일 마지막 객체를
Leaf
라고 합니다.
class Leaf :
public Component
{
public:
Leaf(std::string name)
{
this->setName(name);
}
void print(int idx = 0) override final
{
for (int i = 0; i < idx; i++)
{
std::cout << "\t";
}
std::cout << "File : " << getName() << std::endl;
}
};
복합체 패턴: 실행 코드
- 복합체 패턴은 컴퓨터의 디렉토리 구조와 닮아있습니다.
- 디렉토리는 다른 디렉토리 또는 파일(Leaf)를 포함하고 있습니다.
int main(const int argc, const char* argv[])
{
auto dirRed = std::make_shared<Composite>("Red");
auto dirGreen = std::make_shared<Composite>("Green");
auto dirBlue = std::make_shared<Composite>("Blue");
auto dirBlack = std::make_shared<Composite>("Black");
auto dirWhite = std::make_shared<Composite>("White");
dirBlack->addNode(std::make_shared<Leaf>("A"));
dirBlack->addNode(std::make_shared<Leaf>("B"));
dirBlack->addNode(std::make_shared<Leaf>("C"));
dirRed->addNode(dirGreen);
dirGreen->addNode(dirBlue);
dirBlue->addNode(dirBlack);
dirBlue->addNode(dirWhite);
dirWhite->addNode(std::make_shared<Leaf>("D"));
dirWhite->addNode(std::make_shared<Leaf>("E"));
dirRed->print();
return 0;
}
- 실행 결과
Directory : Red
Directory : Green
Directory : Blue
Directory : Black
File : A
File : B
File : C
Directory : White
File : D
File : E