Architecture & Design Pattern 전체 링크
참고
반복자, 이터레이터 패턴 (Iterator Pattern) - 행동 패턴
- 객체의 컬렉션을 순회하고 요소에 접근할 수 있는 방법을 제공하는 패턴
- 컬렉션의 요소에 대한 구체적인 구현을 노출시키지 않고 컬렉션을 순회
구현
- Iterator (반복자) : 컬렉션의 요소를 순회하고 접근하는 인터페이스, next()와 hasNext()를 제공
- ConcreteIterator : Iterator 인터페이스를 구현하여 실제 컬렉션에 대한 순회 및 접근 동작 구현
- Aggregate (집합체) : 어떤 자료 구조를 활용하여 모아둔 객체, 컬렉션
ㄴ 요소들의 집합을 나타내는 인터페이스를 정의, 이터레이터를 생성하는 메서드를 포함
- ConcreteAggregate : Aggregate 인터페이스를 구현, 이터레이터를 생성하는 메서드를 구현
장점
- SRP(단일 책임 원칙), 순회 알고리즘을 별도의 클래스로 구분하여 클라이언트 코드와 분리한다.
- OCP(개방-폐쇄 원칙), 새로운 컬렉션을 구현하더라도 기존 코드를 건드릴 필요가 없다.
- 컬렉션의 내부 구조를 숨기기 때문에 클라이언트 코드가 단순해진다.
- 컬렉션의 내부 구조를 변경하더라도 클라이언트 코드를 변경하지 않고 순회 알고리즘을 유지한다.
- 컬렉션 순회 코드를 재사용할 수 있고 다양한 순회 방법을 제공할 수 있다.
단점
- 일부 구현에서 추가 오버헤드가 발생할 수 있다.
- 특정 언어에서는 반복자를 지원하지 않을 수도 있다.
빌딩 관리
건물주가 빌딩을 관리하려고 한다.
BuildingInfo 클래스에는 몇 층에 누가 그 건물에 임대 또는 거주하고 있는지에 대한 정보를 가지고 있다.
class BuildingInfo
{
public:
BuildingInfo() = default;
BuildingInfo(int f, string n) : floor(f), name(n) {}
int getFloor() { return floor; }
string getName() { return name; }
private:
int floor;
string name;
};
ostream& operator<<(ostream& os, BuildingInfo& b)
{
os << "(" << b.getFloor() << ") [ " << b.getName() << " ]";
return os;
}
건물주(클라이언트)는 모든 건물의 정보에 일일이 접근하려고 한다.
아파트는 배열(or array)로 만들어져있고, 오피스텔은 vector로 구현되었다.
건물주는 건물이 어떻게 구현되고 저장되는지와 관련 없이 모든 건물에 접근하려고 한다.
위에서 언급한대로 Apart는 배열로 구현되어 있다.
class Apart
{
public:
Apart() { ... }
void addFloor(int f, string n) { ... }
BuildingInfo* getFloors() { return floors; }
private:
static constexpr size_t MAX_FLOOR = 10;
size_t floorCount = 0;
BuildingInfo floors[MAX_FLOOR];
};
Office는 vector로 구현되어 있다.
class Office
{
public:
Office() { ... }
void addFloor(int f, string n) { ... }
vector<BuildingInfo>& getFloors() { return floors; }
private:
vector<BuildingInfo> floors;
};
Apart와 Office의 floors가 각각 다르게 구현되어 있기 때문에 floors 값을 받아 매번 새롭게 구현을 해야 한다.
그리고 구현을 위해 각 항목의 컬렉션이 구체적으로 어떻게 표현되는지 알아야 한다. (캡슐화 X)
// apart와 office 순회가 서로 다르게 구현
BuildingInfo* apartFloors = apart->getFloors();
for (int i = 0; i < 3 /* or getSize() */ ; i++) cout << apartFloors[i] << endl;
vector<BuildingInfo>& officeFloors = office->getFloors();
for (BuildingInfo b : officeFloors) cout << b << endl;
반복자, 이터레이터 패턴을 이용해 아파트와 오피스를 모두 같은 방식으로 접근해보자.
Iterator Interface
반복 작업을 처리하는 방법을 Iterator 객체로 만들면 반복을 캡슐화 할 수 있다.
다음 객체가 있는 동안( while(hasNext()) ) 객체를 처리하고 next()로 다음 객체로 넘어간다.
이제 ApartIterator를 구현한 후, Apart에서 내부 구조를 드러내는 getFloors()는 삭제한다.
그리고 makeIterator로 이터레이터를 생성하는 메서드를 추가한다.
class ApartIterator : public Iterator<BuildingInfo>
{
public:
static constexpr size_t MAX_FLOOR = 10;
ApartIterator(BuildingInfo* b, size_t size) : buildingInfo(b), size(size) {}
BuildingInfo* next() override { return &buildingInfo[index++]; }
bool hasNext() const override { return size > index && buildingInfo[index].getName() != ""; }
private:
BuildingInfo* buildingInfo;
size_t index = 0;
size_t size;
};
class Apart
{
public:
Apart() { ... }
void addFloor(int f, string n) { ... }
unique_ptr<Iterator<BuildingInfo>> makeIterator() { return make_unique<ApartIterator>(floors, floorCount); }
private:
static constexpr size_t MAX_FLOOR = 10;
size_t floorCount = 0;
BuildingInfo floors[MAX_FLOOR];
};
배열 대신 C++ <array>를 이용해서 구현하면 아래와 같다.
class ApartIterator : public Iterator<BuildingInfo>
{
public:
static constexpr size_t MAX_FLOOR = 10;
ApartIterator(array<BuildingInfo, MAX_FLOOR> &b) : buildingInfo(b) {}
BuildingInfo* next() override { return &buildingInfo[index++]; }
bool hasNext() const override { return buildingInfo.size() > index && buildingInfo[index].getName() != ""; }
private:
array<BuildingInfo, MAX_FLOOR> &buildingInfo;
array<BuildingInfo, MAX_FLOOR>::size_type index = 0;
};
class Apart
{
public:
Apart() { ... }
void addFloor(int f, string n) { ... }
unique_ptr<Iterator<BuildingInfo>> makeIterator() { return make_unique<ApartIterator>(floors); }
private:
static constexpr size_t MAX_FLOOR = 10;
size_t floorCount = 0;
array<BuildingInfo, MAX_FLOOR> floors;
};
이제 건물주(Manager)는 Apart와 Office의 이터레이터에 접근해서 각 건물 정보를 출력하면 된다.
class Manager
{
public:
Manager(Apart* apt, Office* ofc) : apart(apt), office(ofc) {}
void printAll() { ... }
void printBuildingInfo(Iterator<BuildingInfo> *iter)
{
while (iter->hasNext())
{
BuildingInfo* b = iter->next();
cout << *b << endl;
}
cout << endl;
}
private:
Apart* apart;
Office* office;
};
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
#include <memory>
#include <array>
#include <vector>
using namespace std;
template <typename T>
class Iterator
{
public:
virtual ~Iterator() = default;
virtual T* next() = 0;
virtual bool hasNext() const = 0;
};
/* ---------------- Building Information ------------------ */
class BuildingInfo
{
public:
BuildingInfo() = default;
BuildingInfo(int f, string n) : floor(f), name(n) {}
int getFloor() { return floor; }
string getName() { return name; }
private:
int floor;
string name;
};
ostream& operator<<(ostream& os, BuildingInfo& b)
{
os << "(" << b.getFloor() << ") [ " << b.getName() << " ]";
return os;
}
/* -------------------------------------------- */
class ApartIterator : public Iterator<BuildingInfo>
{
public:
static constexpr size_t MAX_FLOOR = 10;
ApartIterator(array<BuildingInfo, MAX_FLOOR> &b) : buildingInfo(b) {}
BuildingInfo* next() override { return &buildingInfo[index++]; }
bool hasNext() const override { return buildingInfo.size() > index && buildingInfo[index].getName() != ""; }
private:
array<BuildingInfo, MAX_FLOOR> &buildingInfo;
array<BuildingInfo, MAX_FLOOR>::size_type index = 0;
};
class Apart
{
public:
Apart()
{
addFloor(1, "blood");
addFloor(5, "straw");
addFloor(3, "berry");
}
void addFloor(int f, string n)
{
BuildingInfo b = BuildingInfo(f, n);
if (floorCount >= MAX_FLOOR) cout << "out of floor!!\n" << endl;
else floors[floorCount++] = b;
}
unique_ptr<Iterator<BuildingInfo>> makeIterator() { return make_unique<ApartIterator>(floors); }
private:
static constexpr size_t MAX_FLOOR = 10;
size_t floorCount = 0;
array<BuildingInfo, MAX_FLOOR> floors;
};
/* -------------------------------------------- */
class OfficeIterator : public Iterator<BuildingInfo>
{
public:
OfficeIterator(vector<BuildingInfo> &b) : buildingInfo(b), iter(buildingInfo.begin()) {}
BuildingInfo* next() override { return &*iter++; }
bool hasNext() const override { return iter != buildingInfo.end(); }
private:
vector<BuildingInfo> &buildingInfo;
vector<BuildingInfo>::iterator iter;
};
class Office
{
public:
Office()
{
addFloor(1, "LG");
addFloor(5, "NC");
addFloor(4, "SK");
addFloor(3, "HYUNDAI");
}
void addFloor(int f, string n)
{
BuildingInfo b = BuildingInfo(f, n);
floors.push_back(b);
}
unique_ptr<Iterator<BuildingInfo>> makeIterator() { return make_unique<OfficeIterator>(floors); }
private:
vector<BuildingInfo> floors;
};
/* ----------------- Manager ------------------ */
class Manager
{
public:
Manager(Apart* apt, Office* ofc) : apart(apt), office(ofc) {}
void printAll()
{
unique_ptr<Iterator<BuildingInfo>> apartIter = apart->makeIterator();
cout << "Apart Info" << endl;
printBuildingInfo(apartIter.get());
unique_ptr<Iterator<BuildingInfo>> officeIter = office->makeIterator();
cout << "Office Info" << endl;
printBuildingInfo(officeIter.get());
}
void printBuildingInfo(Iterator<BuildingInfo> *iter)
{
while (iter->hasNext())
{
BuildingInfo* b = iter->next();
cout << *b << endl;
}
cout << endl;
}
private:
Apart* apart;
Office* office;
};
int main(void)
{
Apart* apart = new Apart();
Office* office = new Office();
Manager* manager = new Manager(apart, office);
manager->printAll();
cout << "Apart Info" << endl;
manager->printBuildingInfo(apart->makeIterator().get());
return 0;
}
Aggregate Interface
현재 Manager는 Apart와 Office를 순회하기 위해 아래와 같이 생성자를 만들고 있다.
Manager(Apart* apt, Office* ofc) : apart(apt), office(ofc) {}
Apart와 Office는 똑같은 메서드를 가지고 있기 때문에 같은 인터페이스를 구현하도록 하자.
BuildingInterface 클래스는 makeIterator를 구현하도록 강제한다.
class BuildingInterface
{
public:
virtual ~BuildingInterface() = default;
virtual unique_ptr<Iterator<BuildingInfo>> makeIterator() = 0;
};
Apart 클래스는 BuildingInterface를 상속하고 makeIterator 옆에 override를 추가하였다.
class Apart : public BuildingInterface
{
public:
Apart() { ... }
void addFloor(int f, string n) { ... }
unique_ptr<Iterator<BuildingInfo>> makeIterator() override { return make_unique<ApartIterator>(floors); }
private:
...
};
Manager도 Apart, Office를 BulidingInterface로 변경하였다.
class Manager
{
public:
Manager(BuildingInterface* apt, BuildingInterface* ofc) : apart(apt), office(ofc) {}
...
private:
BuildingInterface* apart;
BuildingInterface* office;
};
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
#include <memory>
#include <array>
#include <vector>
using namespace std;
template <typename T>
class Iterator
{
public:
virtual ~Iterator() = default;
virtual T* next() = 0;
virtual bool hasNext() const = 0;
};
/* ---------------- Building Information ------------------ */
class BuildingInfo
{
public:
BuildingInfo() = default;
BuildingInfo(int f, string n) : floor(f), name(n) {}
int getFloor() { return floor; }
string getName() { return name; }
private:
int floor;
string name;
};
class BuildingInterface
{
public:
virtual ~BuildingInterface() = default;
virtual unique_ptr<Iterator<BuildingInfo>> makeIterator() = 0;
};
ostream& operator<<(ostream& os, BuildingInfo& b)
{
os << "(" << b.getFloor() << ") [ " << b.getName() << " ]";
return os;
}
/* -------------------------------------------- */
class ApartIterator : public Iterator<BuildingInfo>
{
public:
static constexpr size_t MAX_FLOOR = 10;
ApartIterator(array<BuildingInfo, MAX_FLOOR> &b) : buildingInfo(b) {}
BuildingInfo* next() override { return &buildingInfo[index++]; }
bool hasNext() const override { return buildingInfo.size() > index && buildingInfo[index].getName() != ""; }
private:
array<BuildingInfo, MAX_FLOOR> &buildingInfo;
array<BuildingInfo, MAX_FLOOR>::size_type index = 0;
};
class Apart : public BuildingInterface
{
public:
Apart()
{
addFloor(1, "blood");
addFloor(5, "straw");
addFloor(3, "berry");
}
void addFloor(int f, string n)
{
BuildingInfo b = BuildingInfo(f, n);
if (floorCount >= MAX_FLOOR) cout << "out of floor!!\n" << endl;
else floors[floorCount++] = b;
}
unique_ptr<Iterator<BuildingInfo>> makeIterator() override { return make_unique<ApartIterator>(floors); }
private:
static constexpr size_t MAX_FLOOR = 10;
size_t floorCount = 0;
array<BuildingInfo, MAX_FLOOR> floors;
};
/* -------------------------------------------- */
class OfficeIterator : public Iterator<BuildingInfo>
{
public:
OfficeIterator(vector<BuildingInfo> &b) : buildingInfo(b), iter(buildingInfo.begin()) {}
BuildingInfo* next() override { return &*iter++; }
bool hasNext() const override { return iter != buildingInfo.end(); }
private:
vector<BuildingInfo> &buildingInfo;
vector<BuildingInfo>::iterator iter;
};
class Office : public BuildingInterface
{
public:
Office()
{
addFloor(1, "LG");
addFloor(5, "NC");
addFloor(4, "SK");
addFloor(3, "HYUNDAI");
}
void addFloor(int f, string n)
{
BuildingInfo b = BuildingInfo(f, n);
floors.push_back(b);
}
unique_ptr<Iterator<BuildingInfo>> makeIterator() override { return make_unique<OfficeIterator>(floors); }
private:
vector<BuildingInfo> floors;
};
/* ----------------- Manager ------------------ */
class Manager
{
public:
Manager(BuildingInterface* apt, BuildingInterface* ofc) : apart(apt), office(ofc) {}
void printAll()
{
unique_ptr<Iterator<BuildingInfo>> apartIter = apart->makeIterator();
cout << "Apart Info" << endl;
printBuildingInfo(apartIter.get());
unique_ptr<Iterator<BuildingInfo>> officeIter = office->makeIterator();
cout << "Office Info" << endl;
printBuildingInfo(officeIter.get());
}
void printBuildingInfo(Iterator<BuildingInfo> *iter)
{
while (iter->hasNext())
{
BuildingInfo* b = iter->next();
cout << *b << endl;
}
cout << endl;
}
private:
BuildingInterface* apart;
BuildingInterface* office;
};
int main(void)
{
Apart* apart = new Apart();
Office* office = new Office();
Manager* manager = new Manager(apart, office);
manager->printAll();
cout << "Apart Info" << endl;
manager->printBuildingInfo(apart->makeIterator().get());
return 0;
}
ConcreteAggregate는 ConcreteIterator의 인스턴스를 만들어야 하기 때문에 아래의 클래스 다이어그램이 나온다.
map으로 만들어진 상가 건물
이제 map으로 구성된 Store 클래스를 추가해 보자.
class StoreIterator : public Iterator<BuildingInfo>
{
public:
StoreIterator(map<int, BuildingInfo> &b) : buildingInfo(b), iter(buildingInfo.begin()) {}
BuildingInfo* next() override { return &iter++->second; }
bool hasNext() const override { return iter != buildingInfo.end(); }
private:
map<int, BuildingInfo> &buildingInfo;
map<int, BuildingInfo>::iterator iter;
};
class Store : public BuildingInterface
{
public:
Store()
{
addFloor(7, "steak");
addFloor(3, "chicken");
addFloor(1, "fork");
}
void addFloor(int f, string n)
{
BuildingInfo b = BuildingInfo(f, n);
floors[order++] = b; // 입주한 순서대로 BuildingInfo를 저장하는 Store
}
unique_ptr<Iterator<BuildingInfo>> makeIterator() override { return make_unique<StoreIterator>(floors); }
private:
int order = 0;
map<int, BuildingInfo> floors;
};
클라이언트 코드는 Store를 추가하였기 때문에 다음과 같이 변경된다.
int main(void)
{
Apart* apart = new Apart();
Office* office = new Office();
Store* store = new Store();
Manager* manager = new Manager(apart, office, store);
manager->printAll();
return 0;
}
그러나 현재 건물주(Manager)는 Store 클래스가 추가되면서 내부 코드를 수정해야 한다.
class Manager
{
public:
Manager(BuildingInterface* apt, BuildingInterface* ofc, BuildingInterface* sto) : apart(apt), office(ofc), store(sto) {}
void printAll()
{
...
unique_ptr<Iterator<BuildingInfo>> storeIter = store->makeIterator();
cout << "Store Info" << endl;
printBuildingInfo(storeIter.get());
}
void printBuildingInfo(Iterator<BuildingInfo> *iter) { ... }
private:
BuildingInterface* apart;
BuildingInterface* office;
BuildingInterface* store;
};
이 코드는 map<string BuildingType, BuildingInterface>을 이용해서 개선할 수 있다.
Manager를 다음과 같이 수정한다.
class Manager
{
public:
Manager(map<string, unique_ptr<BuildingInterface>>& b) : buildings(b) {}
void printAll()
{
for (const auto& pair : buildings)
{
string buildingType = pair.first;
const unique_ptr<BuildingInterface>& bi = pair.second;
unique_ptr<Iterator<BuildingInfo>> iter = bi->makeIterator();
cout << pair.first << endl;
printBuildingInfo(iter.get());
}
}
void printBuildingInfo(Iterator<BuildingInfo> *iter)
{
while (iter->hasNext())
{
BuildingInfo* b = iter->next();
cout << *b << endl;
}
cout << endl;
}
private:
map<string, unique_ptr<BuildingInterface>>& buildings;
};
이제 클라이언트에 새로운 건물이 추가되어도 Manager의 코드를 변경할 필요가 없다.
int main(void)
{
map<string, unique_ptr<BuildingInterface>> buildings;
buildings["APART"] = make_unique<Apart>();
buildings["OFFICE"] = make_unique<Office>();
buildings["STORE"] = make_unique<Store>();
Manager* manager = new Manager(buildings);
manager->printAll();
return 0;
}
전체 코드는 다음과 같다.
#include <iostream>
#include <string>
#include <memory>
#include <array>
#include <vector>
#include <map>
using namespace std;
template <typename T>
class Iterator
{
public:
virtual ~Iterator() = default;
virtual T* next() = 0;
virtual bool hasNext() const = 0;
};
/* ---------------- Building Information ------------------ */
class BuildingInfo
{
public:
BuildingInfo() = default;
BuildingInfo(int f, string n) : floor(f), name(n) {}
int getFloor() { return floor; }
string getName() { return name; }
private:
int floor;
string name;
};
class BuildingInterface
{
public:
virtual ~BuildingInterface() = default;
virtual unique_ptr<Iterator<BuildingInfo>> makeIterator() = 0;
};
ostream& operator<<(ostream& os, BuildingInfo& b)
{
os << "(" << b.getFloor() << ") [ " << b.getName() << " ]";
return os;
}
/* -------------------------------------------- */
class ApartIterator : public Iterator<BuildingInfo>
{
public:
static constexpr size_t MAX_FLOOR = 10;
ApartIterator(array<BuildingInfo, MAX_FLOOR> &b) : buildingInfo(b) {}
BuildingInfo* next() override { return &buildingInfo[index++]; }
bool hasNext() const override { return buildingInfo.size() > index && buildingInfo[index].getName() != ""; }
private:
array<BuildingInfo, MAX_FLOOR> &buildingInfo;
array<BuildingInfo, MAX_FLOOR>::size_type index = 0;
};
class Apart : public BuildingInterface
{
public:
Apart()
{
addFloor(1, "blood");
addFloor(5, "straw");
addFloor(3, "berry");
}
void addFloor(int f, string n)
{
BuildingInfo b = BuildingInfo(f, n);
if (floorCount >= MAX_FLOOR) cout << "out of floor!!\n" << endl;
else floors[floorCount++] = b;
}
unique_ptr<Iterator<BuildingInfo>> makeIterator() override { return make_unique<ApartIterator>(floors); }
private:
static constexpr size_t MAX_FLOOR = 10;
size_t floorCount = 0;
array<BuildingInfo, MAX_FLOOR> floors;
};
/* -------------------------------------------- */
class OfficeIterator : public Iterator<BuildingInfo>
{
public:
OfficeIterator(vector<BuildingInfo> &b) : buildingInfo(b), iter(buildingInfo.begin()) {}
BuildingInfo* next() override { return &*iter++; }
bool hasNext() const override { return iter != buildingInfo.end(); }
private:
vector<BuildingInfo> &buildingInfo;
vector<BuildingInfo>::iterator iter;
};
class Office : public BuildingInterface
{
public:
Office()
{
addFloor(1, "LG");
addFloor(5, "NC");
addFloor(4, "SK");
addFloor(3, "HYUNDAI");
}
void addFloor(int f, string n)
{
BuildingInfo b = BuildingInfo(f, n);
floors.push_back(b);
}
unique_ptr<Iterator<BuildingInfo>> makeIterator() override { return make_unique<OfficeIterator>(floors); }
private:
vector<BuildingInfo> floors;
};
/* -------------------------------------------- */
class StoreIterator : public Iterator<BuildingInfo>
{
public:
StoreIterator(map<int, BuildingInfo> &b) : buildingInfo(b), iter(buildingInfo.begin()) {}
BuildingInfo* next() override { return &iter++->second; }
bool hasNext() const override { return iter != buildingInfo.end(); }
private:
map<int, BuildingInfo> &buildingInfo;
map<int, BuildingInfo>::iterator iter;
};
class Store : public BuildingInterface
{
public:
Store()
{
addFloor(7, "steak");
addFloor(3, "chicken");
addFloor(1, "fork");
}
void addFloor(int f, string n)
{
BuildingInfo b = BuildingInfo(f, n);
floors[order++] = b; // 입주한 순서대로 BuildingInfo를 저장하는 Store
}
unique_ptr<Iterator<BuildingInfo>> makeIterator() override { return make_unique<StoreIterator>(floors); }
private:
int order = 0;
map<int, BuildingInfo> floors;
};
/* ----------------- Manager ------------------ */
class Manager
{
public:
Manager(map<string, unique_ptr<BuildingInterface>>& b) : buildings(b) {}
void printAll()
{
for (const auto& pair : buildings)
{
string buildingType = pair.first;
const unique_ptr<BuildingInterface>& bi = pair.second;
unique_ptr<Iterator<BuildingInfo>> iter = bi->makeIterator();
cout << pair.first << endl;
printBuildingInfo(iter.get());
}
}
void printBuildingInfo(Iterator<BuildingInfo> *iter)
{
while (iter->hasNext())
{
BuildingInfo* b = iter->next();
cout << *b << endl;
}
cout << endl;
}
private:
map<string, unique_ptr<BuildingInterface>>& buildings;
};
int main(void)
{
map<string, unique_ptr<BuildingInterface>> buildings;
buildings["APART"] = make_unique<Apart>();
buildings["OFFICE"] = make_unique<Office>();
buildings["STORE"] = make_unique<Store>();
Manager* manager = new Manager(buildings);
manager->printAll();
return 0;
}
ClassDiagram.md
```mermaid
classDiagram
class Iterator {
hasNext()*
next()*
}
class ApartIterator {
hasNext()
next()
}
class OfficeIterator {
hasNext()
next()
}
class Manager {
printAll()
}
class Building {
makeIterator()*
}
class Apart {
floors
makeIterator()
}
class Office {
floors
makeIterator()
}
<<Interface>> Iterator
<<Interface>> Building
Iterator <|.. ApartIterator
Iterator <|.. OfficeIterator
Iterator <-- Manager
Building <-- Manager
Building <|.. Apart
Building <|.. Office
```
'개발 > Architecture & Design Pattern' 카테고리의 다른 글
C++ - 데코레이터 패턴 (Decorator Pattern) (1) | 2024.02.10 |
---|---|
C++ - 복합체, 컴포지트 패턴 (Composite Pattern) (1) | 2024.02.09 |
C++ - 옵저버 패턴 (Observer Pattern) (1) | 2024.01.30 |
C++ - 전략, 스트래티지 패턴 (Strategy Pattern) (0) | 2024.01.29 |
C++ - 싱글턴 패턴 (Singleton Pattern) (0) | 2024.01.26 |
댓글