KoreanFoodie's Study
디자인 패턴 (GOF) #2 - Open-Closed Principle (개방-폐쇄 원칙) 본문
Game Dev/Design Patterns
디자인 패턴 (GOF) #2 - Open-Closed Principle (개방-폐쇄 원칙)
GoldGiver 2022. 6. 7. 16:30
GoF 의 디자인 패턴과 강의를 참고하여 디자인 패턴에 대한 내용을 정리하고 있습니다.
Open-Closed Principle (개방-폐쇄 원칙)
OCP 는 확장에는 열려있고, 수정에는 폐쇄적인 디자인 원칙을 의미한다. (Open for extension, Closed for modification)
예를 들어, 다음과 같은 구조의 Product 를 분류해야 하는 코드를 짠다고 가정해 보자. 색깔과 사이즈 등의 기준으로 제품을 분류할 것이다.
struct Product
{
string name;
Color color;
Size size;
};
위 경우, enum class 와 functor 를 사용하면 다음과 같이 코드를 짤 수 있을 것이다.
#include <string>
#include <vector>
#include <iostream>
using namespace std;
enum class Color { red, green, blue };
enum class Size { small, medium, large };
struct Product
{
string name;
Color color;
Size size;
};
struct ProductFilter
{
typedef vector<Product*> Items;
Items by_color(Items items, const Color color)
{
Items result;
for (auto& item : items)
if (item->color == color)
result.push_back(item);
return result;
}
Items by_size(Items items, const Size size)
{
Items result;
for (auto& item : items)
if (item->size == size)
result.push_back(item);
return result;
}
Items by_color_and_size(Items items, const Color color, const Size size)
{
Items result;
for (auto& item : items)
if (item->color == color && item->size == size)
result.push_back(item);
return result;
}
};
int main()
{
Product apple{ "Apple", Color::green, Size::small };
Product tree{ "Tree", Color::green, Size::large };
Product house{ "House", Color::blue, Size::large };
vector<Product*> Products { &apple, &tree, &house };
ProductFilter pf;
// auto filtered = pf.by_color(Products, Color::green);
auto filtered = pf.by_color_and_size(Products, Color::green, Size::large);
for (auto& f : filtered)
cout << f->name << "\n";
}
하지만 이는 매우 비효율적인 코드이다. ProductFilter 의 by_color_and_size 를 보면, 새로운 필터가 추가될 때마다 인자를 늘려나가며 거의 동일한 코드를 복사해야 한다는 것을 확인할 수 있다.
위의 구조 대신, 기존에 작성한 코드를 상속받아 쉽게 확장시킬 수 있도록 코드를 다시 짜 보자.
#include <string>
#include <vector>
#include <iostream>
using namespace std;
enum class Color { red, green, blue };
enum class Size { small, medium, large };
struct Product
{
string name;
Color color;
Size size;
};
template <typename T> struct Specification
{
virtual ~Specification() = default;
virtual bool is_satisfied(T* item) const = 0;
};
template <typename T> struct AndSpecification;
template <typename T> AndSpecification<T> operator&&
(const Specification<T>& first, const Specification<T>& second)
{
return { first, second };
};
template <typename T> struct Filter
{
virtual vector<T*> filter(vector<T*> items,
Specification<T>& spec) = 0;
};
struct BetterFilter : Filter<Product>
{
vector<Product*> filter(vector<Product*> items,
Specification<Product>& spec) override
{
vector<Product*> result;
for (auto& p : items)
if (spec.is_satisfied(p))
result.push_back(p);
return result;
}
};
struct ColorSpecification : Specification<Product>
{
Color color;
ColorSpecification(Color color) : color(color) {}
bool is_satisfied(Product* item) const override
{
return item->color == color;
}
};
struct SizeSpecification : Specification<Product>
{
Size size;
explicit SizeSpecification(const Size size) : size { size } {}
bool is_satisfied(Product* item) const override
{
return item->size == size;
}
};
template <typename T> struct AndSpecification : Specification<T>
{
const Specification<T>& first;
const Specification<T>& second;
AndSpecification(const Specification<T>& first, const Specification<T>& second) : first(first), second(second) {}
bool is_satisfied(T* item) const override
{
return first.is_satisfied(item) && second.is_satisfied(item);
}
};
int main()
{
Product apple{ "Apple", Color::green, Size::small };
Product tree{ "Tree", Color::green, Size::large };
Product house{ "House", Color::blue, Size::large };
vector<Product*> Products { &apple, &tree, &house };
ColorSpecification green(Color::green);
SizeSpecification large(Size::large);
BetterFilter bf;
auto filtered_color = bf.filter(Products, green);
for (auto& f : filtered_color)
cout << f->name << "\n";
// 아래 둘 다 사용 가능
//AndSpecification<Product> green_and_large(green, large);
auto green_and_large = green && large;
auto filtered_color_and_size = bf.filter(Products, green_and_large);
for (auto& f : filtered_color_and_size)
cout << f->name << "\n";
}
템플릿을 활용하여 필터를 쉽게 확장할 수 있도록 만들었다.
이제 Color 와 Size 를 동시에 필터링할 때, 새롭게 코드를 붙여넣을 필요 없이 기존의 코드를 이용하면 된다! (green_and_large 가 어떻게 만들어지는지를 주목)
'Game Dev > Design Patterns' 카테고리의 다른 글
디자인 패턴 (GOF) #5 - Dependency Injection Principle (의존성 역전 원리) (0) | 2022.06.08 |
---|---|
디자인 패턴 (GOF) #4 - Interface Segregation Principle (인터페이스 분리 원칙) (0) | 2022.06.08 |
디자인 패턴 (GOF) #3 - Liskov Substitution Principle (리스코프 치환 원칙) (0) | 2022.06.07 |
디자인 패턴 (GOF) #1 - Single Responsibility Principle (단일 책임 원칙) (0) | 2022.06.07 |
Comments