[모던 c++ 디자인 패턴] 2장, 3장

hyng·2023년 4월 17일
0

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

빌더 패턴

  • 빌더 패턴은 개별 객체의 생성을 별도의 다른 클래스에 위임한다.
    • 흐름식 인터페이스를 이용하면 복잡한 생성 작업을 한 번의 호출 체인으로 처리할 수 있다.
    • 빌더 하나의 인터페이스가 여러 하위 빌더를 노출할 수 있다. 상속과 흐름식 인터페이스를 요령 있게 활용하면, 여러 빌더를 거치는 객체 생성을 쉽게 할 수 있다.
    • 객체의 생성과정이 충분히 복잡할 때에만 해당 패턴이 의미가 있다.

2.6 컴포지트 빌더

  • 객체 하나를 생성하는데 복수의 빌더가 사용되는 경우
  • 개인 신상 정보를 저장하는 프로그램이 있다고 하자
    class Person
    {
    	// 주소
    	std::string street_address, post_code, city;
    	
    	// 직업
    	std::string company_name, position;
    	int annual_income = 0;
    
    	Person() {}
    };
  • 빌더를 각 정보마다 따로 두고 싶다면 API를 어떻게 만드는 것이 가장 편리할까?
    class PersonBuilderBase
    {
    	protected:
    		Person& person; //현재 생성되고 있는 객체에 대한 참조
    		explicit PersonBuilderBase(Person& person) : person{ person }
    		{ } //자식 클래스들에서만 이용할 수 있음
    	public:
    		operator Person()
    		{
    			return std::move(person);
    		}
    
    	PersonAddressBuilder lives() const;
    	PersonJobBuilder works() const;
    };
    
    // 실제 사용자가 이용할 클래스
    class PersonBuilder : public PersonBuilderBase
    {
    	Person p; // 생성 중인 객체
    	public:
    		PersonBuilder() : PersonBuilderBase {p} {}
    };
    
    class PersonAddressBuilder : public PersonBuilderBase
    {
    	typedef PersonAddressBuilder self;
    	public:
    		explicit PersonAddressBuilder(Person& person) : PersonBuilderBase{ person } {}
    		
    		self& at(std::string street_address)
    		{
    			person.street_address = street_address;
    			return *this;
    		}
    		self& with_postcode(std::string post_code) {...}
    		self& in(std::string city) {...}
    };
    
    // PersonJobBuilder도 같은 방식으로 구현된다.
    Person p = Person::create() //빌더를 얻고,
    									 .lives() //PersonAddressBuilder를 얻는다.
    												.at("123 London Road") //주소 정보 설정
    											  .with_postcode("SW1 1GB") //..
    											  .in("London") //..
    									 .works() //PersonJobBuilder를 얻는다.
    												.at("PragmaSoft") //직업 정보 설정
    												.as_a("Consultant") //..
    												.earing(10e6); //..

팩터리 패턴

3.2 팩터리 메서드

어떤 객체를 생성하는지를 이름으로 명확하게 나타낼 수 없는 생성자 대신 객체를 생성하여 리턴하도록 하는 팩터리 메서드를 구현할 수 있다.

3.3 팩터리

빌더와 마찬가지로 Point를 생성하는 함수들을 별도의 클래스에 몰아넣을 수 있다. 그러한 클래스를 팩터리라고 부른다.

struct Point
{
	float x, y;
	friend class PointFactory;
	private:
		Point(float x, float y) : x(x), y(y) {}
};

struct PointFactory
{
	static Point NewCartesian(float x, float y)
	{
		return Point{ x, y };
	}
	static Point NewPolar(float r, float theta)
	{
		return Point{ r*cos(theta), r*sin(theta) };
	}
};

auto my_point = PointFactory::NewCartesian(3, 4);

3.5 추상 팩터리

여러 종류의 연관된 객체들을 생성해야 할 경우도 있다. 추상 팩터리는 그러한 경우를 위한 패턴이다.

class DrinkFactory
{
	map<string, unique_ptr<HotDrinkFactory>> hot_factories;
	public:
		DrinkFactory()
		{
			hot_factories["coffee"] = make_unique<CoffeeFactory>();
			hot_factories["tea"] = make_unique<TeaFactory>();
		}
		unique_ptr<HotDrink> make_drink(const string& name)
		{
			auto drink = hot_factories[name]->make();
			drink->prepare(200);
			return drink;
		}
};

3.6 함수형 팩터리

class DrinkWithVolumeFactory
{
	map<string, function<unique_ptr<HotDrink>()>> factories;
	public:
		DrinkWithVolumeFactory()
		{
			factories["tea"] = [] {
				auto tea = make_unique<Tea>();
				tea->prepare(200);
				return tea;
			};
		}
};

// 저장된 팩터리를 직접 호출하는 과정을 다음과 같이 생략할 수 있다.
inline unique_ptr<HotDrink>
DrinkWithVolumeFactory::make_drink(const string& name)
{
	return factories[name]();
}
profile
공부하고 알게 된 내용을 기록하는 블로그

0개의 댓글