使用 Fluent API 的 Builder 模式

Builder Pattern 將物件的建立與物件本身分離。背後的主要思想是物件不必為自己的建立負責。複雜物件的正確和有效組裝本身可能是一項複雜的任務,因此可以將此任務委派給另一個類。

C#中Email Builder 的啟發,我決定在這裡製作一個 C++版本。Email 物件不一定是非常複雜的物件,但它可以演示模式。

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

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

對於舊版本的 C++,可以忽略 std::move 操作並從轉換運算子中刪除&&(儘管這將建立臨時副本)。

構建器在釋出 operator Email&&() 構建的電子郵件時完成其工作。在此示例中,構建器是臨時物件,並在銷燬之前返回電子郵件。你還可以使用像 Email EmailBuilder::build() {...} 這樣的顯式操作,而不是轉換運算子。

通過建設者

Builder Pattern 提供的一個很棒的功能是能夠**使用多個 actor 一起構建一個物件。**這是通過將構建器傳遞給其他 actor 來完成的,每個 actor 都會向構建的物件提供更多資訊。當你構建某種查詢,新增過濾器和其他規範時,這非常強大。

void add_addresses(EmailBuilder& builder)
{
    builder.from("me@mail.com")
           .to("you@mail.com");
}

void compose_mail(EmailBuilder& builder)
{
    builder.subject("I know the subject")
           .body("And the body. Someone else knows the addresses.");
}

int main()
{
    EmailBuilder builder;
    add_addresses(builder);
    compose_mail(builder);
    
    Email mail = builder;
    cout << mail << endl;
}

設計變體:可變物件

你可以更改此模式的設計以滿足你的需求。我會給一個變種。

在給定的示例中,Email 物件是不可變的,即,它的屬性無法修改,因為無法訪問它們。這是一個理想的功能。如果你需要在建立物件後對其進行修改,則必須為其提供一些 setter。由於這些 setter 將在構建器中重複,因此你可以考慮在一個類中完成所有操作(不再需要構建器類)。不過,我認為首先需要使構建的物件變得可變。