모던 c++ 디자인 패턴 을 읽고 인상 깊었던 내용을 정리합니다.
struct Shape
{
protected:
Renderer& renderer; //서로의 존재를 알아야만 한다.
Shape(Renderer& renderer) : renderer{ renderer } {}
public:
virtual void draw() = 0;
virtual void resize(float factor) = 0;
};
매개자 패턴은 서로를 직접적으로 알지 못하더라도 서로 연동할 수 있게 해준다.
class Creature
{
enum Abilities { str, agl, intl, count };
array<int, count> abilities;
int get_strength() const { return abilities[str]; }
void set_strength(int value) { abilities[str] = value; }
int sum() const { // 새로운 속성이 추가되더라도 코드의 변경은 없다.
return accumulate(abilities.begin(), abilities.end(), 0);
}
double average() const { // 마찬가지로 코드 변경 없음.
return sum() / (double)count;
}
};
데커레이터 패턴은 이미 존재하는 타입에 새로운 기능을 추가 하면서도 원래 타입의 코드에 수정을 피할 수 있게 해준다.
struct ColoredShape : Shape
{
Shape& shape;
string color;
ColoredShape(Shape& shape, const string& color) : shape{shape}, color{color} {}
string str() const override
{
ostringstream oss;
oss << shape.str() << "has the color " << color;
return oss.str();
}
}
Circle circle{3};
ColoredShape redCircle{circle, "red"};
redCircle.resize(2); //resize()는 Circle의 함수, Shape 인터페이스에 없기 때문에 호출 x
데커레이션된 객체의 멤버 함수와 필드에 모두 접근할 수 있어야 한다면 어떻게 해야 할까?
새로운 클래스 ColoredShape를 만들고 템플릿 인자로 받은 클래스를 상속받게 한다.
// 템플릿 파라미터를 제약할 방법은 없기 때문에 static_assert를 이용해 Shape 이외의 타입이 지정되는것을 막는다.
template <typename T>
struct ColoredShape : T
{
static_assert(is_base_of<Shape, T>::value, "Template argument must be a Shape");
string color;
string str() const override
{
ostringstream oss;
oss << T::str() << " has the color" << color;
return oss.str();
}
};
ColoredShape와 TransparentShape 의 구현을 기반으로 하여 색상이 있는 투명한 도형을 생성할 수 있다.
ColoredShape<TransparentShape<Square>> square{"blue"};
square.size = 2;
square.transparency = 0.5;
cout << square.str();
square.resize(3);
특정 코드를 수행하기 전과 후에 로그를 남기고 싶다고 하자.
cout << "Entering function\n";
// 작업 수행
cout << "Exiting function\n";
이런 방식은 익숙하고 잘 동작하지만, 로깅 기능을 분리하여 재사용 할 수 있다면 훨씬 더 좋을 것이다.
한 가지 방법은 전체 코드 단위를 로깅 컴포넌트에 람다로서 넘기는 것이다.
struct Logger
{
function<void()> func;
string name;
Logger(const function<void()>& func, const string& name) : func{func}, name{name}
{
}
void operator()() const
{
cout << "Entering " << name << endl;
func();
cout << "Entering " << name << endl;
}
};
로깅 컴포넌트는 다음과 같이 활용할 수 있다.
Logger([]() {cout<<"Hello"<<endl;}, "HelloFunction")();
//출력 결과
// Entering HelloFunction
// Hello
// Exiting HelloFunction
코드 블록을 템플릿 인자로 전달할 수도 있다.
template <typename Func>
struct Logger2
{
Func func;
string name;
Logger2(const Func& func, const string& name) : func{func}, name{name} {}
void operator()() const
{
cout << "Entering " << name << endl;
func();
cout << "Exiting " << name << endl;
}
};
//로깅 인스턴스를 생성하기 위해 다음과 같은 편의함수를 만든다.
template <typename Func>
auto make_logger2(Func func, const string& name)
{
return Logger2<Func>{ func, name };
}
//실행 및 결과
auto call = make_logger2([]() { cout << "Hello!" << endl;}, "HelloFunction" );
call();