C++ Builder 패턴

복잡한 객체를 단계적으로 구성하는 Builder 패턴입니다. Fluent API 메서드 체이닝과 Director 클래스를 활용한 HTTP 요청 빌더를 예시로 구현합니다.

Gist
#include <iostream>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include <sstream>
#include <stdexcept>

// ─────────────────────────────────────────────
// Product: HTTP 요청 객체
// Builder가 최종적으로 생성하는 복잡한 객체
// ─────────────────────────────────────────────
class HttpRequest {
public:
    void print() const {
        std::cout << method_ << " " << url_ << " HTTP/1.1\n";
        for (const auto& [k, v] : headers_) {
            std::cout << "  " << k << ": " << v << "\n";
        }
        if (!queryParams_.empty()) {
            std::cout << "  쿼리 파라미터:\n";
            for (const auto& [k, v] : queryParams_) {
                std::cout << "    " << k << "=" << v << "\n";
            }
        }
        if (!body_.empty()) {
            std::cout << "  본문: " << body_ << "\n";
        }
        std::cout << "  타임아웃: " << timeoutMs_ << "ms\n";
    }

    // Builder만 내부 상태를 설정할 수 있도록 friend 선언
    friend class HttpRequestBuilder;

private:
    std::string method_{"GET"};
    std::string url_;
    std::unordered_map<std::string, std::string> headers_;
    std::unordered_map<std::string, std::string> queryParams_;
    std::string body_;
    int timeoutMs_{5000};
};

// ─────────────────────────────────────────────
// Builder: Fluent API로 HttpRequest를 단계별 구성
// 각 메서드가 *this를 반환 → 메서드 체이닝 가능
// ─────────────────────────────────────────────
class HttpRequestBuilder {
public:
    // HTTP 메서드 설정
    HttpRequestBuilder& method(const std::string& m) {
        request_.method_ = m;
        return *this;
    }

    // 대상 URL 설정
    HttpRequestBuilder& url(const std::string& u) {
        request_.url_ = u;
        return *this;
    }

    // 헤더 추가 (누적 가능)
    HttpRequestBuilder& header(const std::string& key, const std::string& value) {
        request_.headers_[key] = value;
        return *this;
    }

    // Bearer 토큰 인증 헤더 편의 메서드
    HttpRequestBuilder& bearerAuth(const std::string& token) {
        return header("Authorization", "Bearer " + token);
    }

    // JSON Content-Type 설정 편의 메서드
    HttpRequestBuilder& jsonContentType() {
        return header("Content-Type", "application/json");
    }

    // 쿼리 파라미터 추가
    HttpRequestBuilder& queryParam(const std::string& key, const std::string& value) {
        request_.queryParams_[key] = value;
        return *this;
    }

    // 요청 본문 설정
    HttpRequestBuilder& body(const std::string& b) {
        request_.body_ = b;
        return *this;
    }

    // 타임아웃 설정 (밀리초)
    HttpRequestBuilder& timeout(int ms) {
        if (ms <= 0) throw std::invalid_argument("타임아웃은 양수여야 합니다.");
        request_.timeoutMs_ = ms;
        return *this;
    }

    // 유효성 검사 후 최종 객체 반환 (빌더 소비)
    HttpRequest build() {
        if (request_.url_.empty()) {
            throw std::runtime_error("URL은 필수 값입니다.");
        }
        return std::move(request_);
    }

private:
    HttpRequest request_;
};

// ─────────────────────────────────────────────
// Director: 자주 사용하는 요청 조합을 미리 정의
// Builder의 단계 순서를 캡슐화
// ─────────────────────────────────────────────
class HttpRequestDirector {
public:
    // JSON API 인증 GET 요청 템플릿
    static HttpRequest makeAuthenticatedGet(
        const std::string& url, const std::string& token)
    {
        return HttpRequestBuilder{}
            .method("GET")
            .url(url)
            .bearerAuth(token)
            .header("Accept", "application/json")
            .timeout(3000)
            .build();
    }

    // JSON POST 요청 템플릿
    static HttpRequest makeJsonPost(
        const std::string& url,
        const std::string& token,
        const std::string& jsonBody)
    {
        return HttpRequestBuilder{}
            .method("POST")
            .url(url)
            .bearerAuth(token)
            .jsonContentType()
            .body(jsonBody)
            .timeout(10000)
            .build();
    }
};

// ─────────────────────────────────────────────
// 사용 예시
// ─────────────────────────────────────────────
int main() {
    std::cout << "=== 직접 빌더 체이닝 ===\n";
    // Fluent API로 세밀하게 커스터마이징
    HttpRequest customReq = HttpRequestBuilder{}
        .method("GET")
        .url("https://api.example.com/users")
        .bearerAuth("eyJhbGciOiJIUzI1NiJ9.token")
        .header("Accept-Language", "ko-KR")
        .queryParam("page", "1")
        .queryParam("limit", "20")
        .timeout(8000)
        .build();
    customReq.print();

    std::cout << "\n=== Director를 통한 표준 GET 요청 ===\n";
    // Director: 미리 정의된 패턴 재사용
    HttpRequest getReq = HttpRequestDirector::makeAuthenticatedGet(
        "https://api.example.com/products",
        "my-secret-token"
    );
    getReq.print();

    std::cout << "\n=== Director를 통한 JSON POST 요청 ===\n";
    HttpRequest postReq = HttpRequestDirector::makeJsonPost(
        "https://api.example.com/orders",
        "my-secret-token",
        R"({"productId": 42, "quantity": 3})"
    );
    postReq.print();

    return 0;
}