C++ - 방문자, 비지터 패턴 (Visitor Pattern)
Architecture & Design Pattern 전체 링크
참고
방문자, 비지터 패턴 (Visitor Pattern) - 행동 패턴
- 객체의 구조와 동작을 분리하여 새로운 동작을 추가하는 패턴
- 어떤 클래스에 대해 특정 메서드를 분리하고 싶을 때 사용할 수 있다.
구현
- Visitor : 새로운 동작을 정의하는 인터페이스, 특정 작업을 수행하는 메서드가 정의된다.
- Element : Visitor를 수용하는 인터페이스, accept 메서드로 Visitor를 받는다.
- ConcreteVisitor : 실제로 새로운 동작을 구현한다.
- ConcreteElement : Visitor가 새로운 동작을 수행하는 실제 객체가 된다.
장점
- SRP(단일 책임 원칙), 관련 있는 메서드를 하나의 Visitor에 모을 수 있다.
- OCP(개방-폐쇄 원칙), 클래스를 변경하지 않고 새로운 행동을 추가할 수 있다.
단점
- Visitor가 다루어야 할 멤버 객체에 대한 접근 권한이 제한될 수 있다.
- 추가적인 클래스와 인터페이스로 코드의 복잡성이 증가한다.
- 객체 내부 상테에 직접 접근하는 경우, 캡슐화에 위배될 수 있다.
- 객체의 동작이 아닌 구조를 변경하는 경우, 비지터 패턴도 수정해야 한다.
건물 이름 출력하기
컴포지트 패턴에 이터레이터를 적용해서 건물을 순회하였다.
이제 여기서 건물 이름을 모두 소문자 또는 대문자로 출력하고 싶다고 가정하자.
이러한 메소드를 BuildingComponent에 추가해도 되지만,
건물 이름을 소문자 또는 대문자로 출력하는 것은 건물 고유의 특징과는 관련이 없는 행동이다.
즉, 클래스와 메서드가 서로 크게 관련이 없다.
이런 경우 비지터 패턴을 이용해서 클래스의 구조는 변경하지 않고, 메서드를 추가할 수 있다.
비지터 패턴 적용
기존 클래스(Element)는 변경하고 싶지 않지만,
최초 한 번은 비지터 패턴을 이용하기 위해서 accept 메서드를 추가해야 한다.
Element는 accept 메서드를 이용하여 Visitor에 정의된 메서드를 실행하게 된다.
먼저 Visitor 인터페이스를 정의하자.
class Visitor
{
public:
virtual void visit(BuildingComponent *element) = 0;
};
그리고 BuildingComponent(= Element)는 accept 메서드를 정의한다.
class BuildingComponent
{
public:
virtual ~BuildingComponent() = default;
virtual Iterator<BuildingComponent>* createIterator() = 0;
virtual void accept(Visitor *visitor) = 0;
...
};
BuildingComponent에서 accept를 순수 가상함수로 정의했으므로, Composite와 Leaf에서 실제로 구현해야 한다.
여기서는 간단히 Visitor의 메서드를 호출하도록 하였다.
class BuildingComposite : public BuildingComponent
{
public:
BuildingComposite() = default;
BuildingComposite(string type) : buildingType(type) {}
void accept(Visitor *visitor) override { visitor->visit(this); }
...
};
class BuildingLeaf : public BuildingComponent
{
public:
BuildingLeaf() = default;
BuildingLeaf(int f, string n) : floor(f), name(n) {}
void accept(Visitor *visitor) override { visitor->visit(this); }
...
};
이제 ConcreteVisitor를 구현해 보자.
Building의 이름을 소문자로 출력하는 Visitor는 다음과 같이 구현할 수 있다.
getName은 throw가 발생할 수 있으므로 try ~ catch로 처리하였다.
class LowerCaseVisitor : public Visitor
{
public:
void visit(BuildingComponent *element) override
{
try
{
string change = element->getName();
transform(change.begin(), change.end(), change.begin(), ::tolower);
cout << "Lower Name : " << change << endl;
}
catch (invalid_argument e) {}
}
};
이제 이터레이터를 이용하여 LowerCaseVisitor와 UpperCaseVisitor를 accept에 넘겨주자.
int main(void)
{
...
Iterator<BuildingComponent>* iterator = allBuildings->createIterator();
cout << "Print Name Upper / Lower Case" << endl;
while (iterator->hasNext())
{
BuildingComponent* bc = iterator->next();
bc->accept(new LowerCaseVisitor());
bc->accept(new UpperCaseVisitor());
}
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;
}
건물의 이름이 정상적으로 출력된다.
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
#include <memory>
#include <vector>
#include <list>
#include <stack>
#include <algorithm> // for upper case
using namespace std;
class BuildingComponent;
class Visitor
{
public:
virtual void visit(BuildingComponent *element) = 0;
};
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 accept(Visitor *visitor) = 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) {}
void accept(Visitor *visitor) override { visitor->visit(this); }
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) {}
void accept(Visitor *visitor) override { visitor->visit(this); }
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;
};
/* ------------ Concrete Visitor ------------- */
class LowerCaseVisitor : public Visitor
{
public:
void visit(BuildingComponent *element) override
{
try
{
string change = element->getName();
transform(change.begin(), change.end(), change.begin(), ::tolower);
cout << "Lower Name : " << change << endl;
}
catch (invalid_argument e) {}
}
};
class UpperCaseVisitor : public Visitor
{
public:
void visit(BuildingComponent *element) override
{
try
{
string change = element->getName();
transform(change.begin(), change.end(), change.begin(), ::toupper);
cout << "Upper Name : " << change << endl;
}
catch (invalid_argument e) {}
}
};
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);
// ------------------------------------------------------------------ //
Iterator<BuildingComponent>* iterator = allBuildings->createIterator();
cout << "Print Name Upper / Lower Case" << endl;
while (iterator->hasNext())
{
BuildingComponent* bc = iterator->next();
bc->accept(new LowerCaseVisitor());
bc->accept(new UpperCaseVisitor());
}
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;
}
ClassDiagram.md
```mermaid
classDiagram
class Visitor {
visit(Element)*
}
class ConcreteVisitorA {
visit(Element)
}
class ConcreteVisitorB {
visit(Element)
}
class Element {
accept(Visitor)
}
class ConcreteElementA {
accept(Visitor)
}
class ConcreteElementB {
accept(Visitor)
}
<<interface>> Visitor
<<interface>> Element
Client --> Visitor
Client --> Element
Visitor <|.. ConcreteVisitorA
Visitor <|.. ConcreteVisitorB
Visitor <-- Element
Element <|.. ConcreteElementA
Element <|.. ConcreteElementB
```