상세 컨텐츠

본문 제목

빌더 패턴 Build pattern

컴퓨터 언어/디자인패턴

by cepiloth 2021. 5. 7. 11:47

본문

728x90
반응형

 

 빌더 패턴은(Build pattern)은 생성이 까다로운 객체를 쉽게 처리하기 위한 패턴이다. 생성자 호출 코드 단 한 줄로 생성할 수 없는 객체를 다룬다. 그러한 타입의 객체들은 다른 객체들의 조합으로 구성되거나, 상식적인 것을 벗어난 까다로운 로직을 필요로 한다. 이러한 객체를 생성하는 코드는 따로 분리되어 관리될 필요가 있다.

 

예제

 웹 페이지를 그리기 위한 컴포넌트들을 생성해야 한다고 하자. 먼저, 단순하게 단어를 나타내는 항목 두 개("hello"와 "world")를 html의 비 순차("<ul>") 리스트 ("<li>") 태그로 출력해보자. 가장 단순하게 구현한다면 다음과 같이 할 수 있다.​

string words[] = {"hello", "world"};
osstringstream oss;
oss << "<ul>";

for(auto w : words)
{
oss << "  <li>" << w << "</li>";
oss << "</ul>";
}

print(oss.str().c_str());

 

 이 코드는 목적한 대로 출력을 하기는 한다. 하지만 융통성은 없다. 항목마다 앞에 점을 찍거나 순서대로 번호를 매겨야 한다면 이 코드를 어떻게 수정해야 할까? 새로운 항목을 리스트의 끝에 추가해야 한다면 코드를 어떻게 수정해야 할까? 이렇게 고정된 형태로는 손쉽게 대응하기 어렵다.

대안으로 객체 지향(OOP) 스타일을 채용할 수 있다. HtmlElement 클래스를 정의하여 각 html 태그에 대한 정보를 저장한다.

// 선언부
struct HtmlElement
{
	std::string name;
	std::string text;

	std::vector<HtmlElement> elements;

	HtmlElement() {}
	HtmlElement(const std::string& name, const std::string& text)
		: name(name)
		, text(text) {}

	std::string str(int indent = 0) const 
	{
		std::string s;
		for (auto e : elements) {
			s += e.name;
			s += " tag : ";
			s += e.text;
			s += "\n";
		}
		return s;
	}
};

// USAGE
string words[] = {"hello", "world"};
HtmlElement list{"ul", ""};

for (auto w : words)
	list.elements.emplace_back{HtmlElement{"li", w}};

print(list.str().c_str());

 위 코드는 OOP에 기반하여 항목 리스트를 표현한다. 양식 제어하기가 좀 더 쉬우면서도 목적하는 출력을 할 수 있다. 하지만 각각의 HtmlElement를 생성하는 작업이 편리하지 않다. 이 부분을 빌더 패턴을 활용해보자.

 

Simle Builder

 HtmlBuilder는 HTML 구성 요소의 생성만을 전담하는 클래스이다. add_child() 메서드는 현재 요소에 자식 요소를 추가하는 목적으로 사용된다. 각자식 요소는 이름/텍스트 쌍을 가진다. 이 클래스는 아래와 같이 이용된다.

struct HtmlBuilder
{
	HtmlElement root;

	HtmlBuilder(std::string root_name) { root.name = root_name; }

	void add_child(std::string child_name, std::string child_text) {
		HtmlElement e{ child_name, child_text };
		root.elements.emplace_back(e);
	}

	std::string str() { 
		return root.str(); 
	}
};

 

add_child() 메서드는 리턴 값은 사용되는 곳 없이 void 선언되어 있다. 리턴 값을 활용하면 좀 더 편리한 흐름식 인터페이스(fluent interface) 스타일의 빌더를 만들 수 있다.

HtmlBuilder builder{ "ui" };
builder.add_child("li", "hello");
builder.add_child("li", "world");
cout << builder.str() << endl;

 

Fluent Builder

다음 과같이 빌더 자기 자신을 리턴하도록 add_child()의 정의를 수정해보자.

struct HtmlFluentBuilder
{
	HtmlElement root;

	HtmlFluentBuilder(std::string root_name) { root.name = root_name; }

	HtmlFluentBuilder& add_child(std::string child_name, std::string child_text) {
		HtmlElement e{ child_name, child_text };
		root.elements.emplace_back(e);
		return *this;
	}

	std::string str() {
		return root.str();
	}
};

 

 빌더 자기 자신이 참조로서 리턴되기 때문에 다음과 같이 메서드들이 꼬리를 무는 호출이 가능해진다. 이러한 형태로 호출하는 것을 흐름식 인터페이스(fluent interface)라고 부른다.

HtmlFluentBuilder builder{ "ui" };
builder.add_child("li", "hello").add_child("li", "world");
cout << builder.str() << endl;

 

리턴을 참조로 할지 포인터로 할지는 개발자의 자유이다. 호출 체인에 -> 연산자를 사용하고 싶다면 다음과 같이 add_child()를 수정한다.

HtmlFluentBuilder* add_child(std::string child_name, std::string child_text) {
	HtmlElement e{ child_name, child_text };
	root.elements.emplace_back(e);
	return this;
}

 

그러면 다음과 같이 이용할 수 있다.

HtmlFluentBuilder *builder = new HtmlBuilder("ui");
builder->add_child("li", "hello")->add_child("li", "world");
cout << builder << endl;

 

객체 생성 의도 설정

 HTML 구성 요소의 생성을 전담하는 빌더 클래스를 만들었다. 그런데 사용자가 빌더 클래스를 사용해야 한다는 것을 어떻게 알 수 있을까? 한 가지 방법은 빌더를 사용 안 하면 객체 생성이 불가능하도록 강제하는 것이다.

struct HtmlElement
{
	std::string name;
	std::string text;

	std::vector<HtmlElement> elements;

	static std::unique_ptr<HtmlElement> buildd(const std::string& root_name)
	{
		return std::make_unique<HtmlElement>(root_name);
	}

protected: // 모든 생성자 숨기기
	HtmlElement() {}
	HtmlElement(const std::string& name, const std::string& text)
		: name(name)
		, text(text) {}

	std::string str(int indent = 0) const 
	{
		std::string s;
		for (auto e : elements) {
			s += e.name;
			s += " tag : ";
			s += e.text;
			s += "\n";
		}
		return s;
	}
};

 

흐름식 빌더(Fluent Build) 예시 2

riptutorial.com/cplusplus/example/30166/builder-pattern-with-fluent-api

 

C++ - Builder Pattern with Fluent API | c++ Tutorial

c++ documentation: Builder Pattern with Fluent API

riptutorial.com

 

필자가 주로 사용하는 빌더 패턴 코드이다. 위 사이트의 코드를 참고한다.

#pragma once
#include <iostream>
#include <sstream>
#include <string>

using namespace std;

// Forward declaring the builder
class EmailBuilder;

class Email
{
public:
    friend class EmailBuilder;  // the builder can access Email's privates

    static EmailBuilder make();

    string to_string() const {
        stringstream stream;
        stream << "from: " << m_from
            << "\nto: " << m_to
            << "\nsubject: " << m_subject
            << "\nbody: " << m_body;
        return stream.str();
    }

private:
    Email() = default; // restrict construction to builder

    string m_from;
    string m_to;
    string m_subject;
    string m_body;
};

class EmailBuilder
{
public:
    EmailBuilder& from(const string& from) {
        m_email.m_from = from;
        return *this;
    }

    EmailBuilder& to(const string& to) {
        m_email.m_to = to;
        return *this;
    }

    EmailBuilder& subject(const string& subject) {
        m_email.m_subject = subject;
        return *this;
    }

    EmailBuilder& body(const string& body) {
        m_email.m_body = body;
        return *this;
    }

    operator Email && () {
        return std::move(m_email); // notice the move
    }

private:
    Email m_email;
};

EmailBuilder Email::make()
{
    return EmailBuilder();
}

// Bonus example!
std::ostream& operator <<(std::ostream& stream, const Email& email)
{
    stream << email.to_string();
    return stream;
}

 

실제 코드를 사용할때는 아래와 같이 사용할 수 있다.

#include <iostream>
using namespace std;

#include "EmailBuilder.h"

int main()
{
	// 흐름식 빌더
    Email mail = Email::make().from("me@mail.com")
        .to("you@mail.com")
        .subject("C++ builders")
        .body("I like this API, don't you?");

    cout << mail << endl;
	return 0;
}

 

Discussion

빌더 패턴은 안드로이드(Java)에서 매우 편하게 사용하였었는데 C++에서는 조금 구현에 애매한 점이 있었다. 개인적으로 C++에서 주로 흐름식(Feunt) 빌더 패턴을 선호한다.

728x90
반응형

'컴퓨터 언어 > 디자인패턴' 카테고리의 다른 글

RAII idiom  (0) 2021.05.07
핌플 이디엄 Pimpl idiom  (0) 2021.05.07
싱글턴 패턴 SingleTon pattern  (0) 2021.05.07
빌더 패턴 사용 이유  (0) 2021.05.07
모노스테이트 패턴 Monostate pattern  (0) 2021.05.07

관련글 더보기

댓글 영역