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 가 어떻게 만들어지는지를 주목)

 
Comments