[모던 c++ 디자인 패턴] 6-9장

hyng·2023년 4월 20일
0

모던 c++ 디자인 패턴 을 읽고 인상 깊었던 내용을 정리합니다.

브릿지

struct Shape
{
	protected:
		Renderer& renderer; //서로의 존재를 알아야만 한다.
		Shape(Renderer& renderer) : renderer{ renderer } {}
	public:
		virtual void draw() = 0;
		virtual void resize(float factor) = 0;
};

매개자 패턴은 서로를 직접적으로 알지 못하더라도 서로 연동할 수 있게 해준다.

컴포지트

8.1 배열에 기반한 속성

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;
	}
};

데커레이터

데커레이터 패턴은 이미 존재하는 타입에 새로운 기능을 추가 하면서도 원래 타입의 코드에 수정을 피할 수 있게 해준다.

9.3 정적 데커레이터

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);

9.4 함수형 데커레이터

특정 코드를 수행하기 전과 후에 로그를 남기고 싶다고 하자.

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();
profile
공부하고 알게 된 내용을 기록하는 블로그

0개의 댓글