[모던 c++ 디자인 패턴] ~24장

hyng·2023년 4월 27일
0

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

관찰자

20.7 재진입성

운전면허 관제를 위한 컴포넌트 TrafficAdministration가 있다고 하자. 이 컴포넌트는 기준 연령에 도달했다면 모니터링을 중단하기 위해 알림 수신 등록을 해제한다.

struct TrafficAdministration : Observer<Person>
{
	void TrafficAdministration::field_changed(Person& source, const string& field_name) override
	{
		if (source.get_age() < 17) {
			cout << "Whoa there, you are not enough to drive!\n";
		}
		else {
			cout << "We no longer care!\n";
			source.unsubscribe(this);
		}
	}
};
	

만약 17세 이상이라면 다음의 순서로 함수가 호출된다.

💡 notify() → field_changed() → unsubscribe()

여기서 unsubscribe()의 실행이 이미 앞에서 락이 점유된 이후에 일어나고 있기 때문에 이미 점유된 락을 다시 점유하려 들게 된다.

해결 방법은 여러가지가 있고, 그중 한가지로 notify() 안에서 컬렉션 자체를 복제하여 사용하는 것이 있다.

void notify(T& source, const string& name)
{
	vector<Observer<T>*> observers_copy; // 포인터를 저장하고 있기 때문에 점유하는 메모리양이 문제될 만큼 크지 않다.
	{
		lock_quard<mutex_t> lock { mtx }; //스코프를 벗어날 때 소멸자를 통해 락이 해제된다.
		observers_copy = observers;
	}
	for (auto obs : observers_copy) {
		if (obs) {
			obs -> field_changed(source, name); // 해당 함수가 호출되는 시점에는 락이 해제된 상태.
		}
	}
}
💡 명시적으로 unsubscribe() 함수를 제공해야 한다면 그 함수 안에서 “삭제하고 이동하기” 관례를 사용하지 않는 것이 바람직하다. 대신 삭제할 항목을 표시만 해 두고 실제 삭제는 나중에 하는 것이 까다로운 시나리오에 대응하기에 좋다.

전략

목록을 출력하는데 어떤 때는 HTML로, 또 어떤 때는 Markdown 형식으로 출력해야 한다고 하자.

즉, 때에 따라 전략이 달라진다.

22.1 동적 전략

enum class OutputFormat
{
	markdown,
	html
};

struct ListStrategy
{
	virtual void start(ostringstream& oss) {];
	virtual void add_list_item(ostringstream& oss, const string& item) {}
	virtual void end(ostringstream& oss) {};
};

텍스트 처리 컴포넌트로, 목록 처리를 위한 멤버 함수를 갖는다.

struct TextProcessor
{
	void append_list(const vector<string> items)
	{
		list_strategy->start(oss); //목록을 출력할 전략을 전달받아, 함수 호출
		for (auto& item : items)
			list_strategy->add_list_item(oss, item);
		list_strategy->end(oss);
	}
	private:
		ostringstream oss;
		unique_ptr<ListStrategy> list_strategy;
};

HTML 형식으로 목록을 나타내는 HtmlListStrategy

struct HtmlListStrategy : ListStrategy
{
	void start(ostringstream& oss) override
	{
		oss << "<ul>\n";
	}
	void end(ostringstream& oss) override
	{
		oss << "</ul>\n";
	}
	void add_list_item(ostringstream& oss, const string& item) override
	{
		oss << "<li>" << item << "</li>\n";
	}
};
			

Markdown 방식으로 목록을 나타내는 MarkdownListStrategy

struct MarkdownListStrategy : ListStrategy
{
	void add_list_item(ostringstream& oss, const string& item) override
	{
		oss << " * " << item << endl;
	}
};

서로 다른 전략에 목록을 입력하여 서로 다른 렌더링 결과를 얻을 수 있다.

TextProcessor tp;
tp.set_output_format(OutputFormat::markdown);
tp.append_list({"foo", "bar", "baz"});
cout << tp.str() << endl;

22.2 정적 전략

템플릿을 사용해 자동으로 타입에 맞추어 전략을 적용할 수 있다.

template <typename LS>
struct TextProcessor
{
	void append_list(const vector<string> items)
	{
		list_strategy.start(oss);
		for (auto& item : items)
			list_strategy.add_list_item(oss, item);
		list_strategy.end(oss);
	}
	private:
		ostringstream oss;
		LS list_strategy;
};

출력 결과는 동일하나 목록 렌더링 전략 마다 개별적으로 인스턴스를 가져야만 한다.

profile
공부하고 알게 된 내용을 기록하는 블로그

0개의 댓글