Różnice w Builder Pattern

0

Ostatnio zastanawiałem się nad wzorcem budowniczego bo już na kilku stronach widziałem, że przykłady ilustrują trochę inne podejście niż to jakie używam od zawsze. A mianowicie z poczciwą metodą build() jako ostatnia metoda w łańcuchu metod. No to może przykłady.
Najpierw stary sposób:

#include <utility>
#include <string>

class Person;

class PersonBuilder {
    friend class Person;
public:
    PersonBuilder& setName(const std::string& name) { this->name = name; return *this; }
    Person build();

private:
    std::string name;
};

class Person {
public:
    Person() = delete;
    Person(const PersonBuilder& builder) : name(std::move(builder.name)) {}

private:
    std::string name;
};

Person PersonBuilder::build() { return Person(*this); }

int main(int argc, char *argv[]) {
    Person  pereson = PersonBuilder().setName("Jan").build();
    return 0;
}

Wariant starego sposobu:

#include <utility>
#include <string>

class Person {
    friend class PersonBuilder;
private:
    Person() = default;

public:
    Person(const Person& person) = default;

private:
    std::string name;
};

class PersonBuilder {
    friend class Person;
public:
    PersonBuilder& setName(const std::string& name) { person.name = name; return *this; }
    Person build() { return std::move(person); }

private:
    Person person;
};

int main(int argc, char *argv[]) {
    Person person = PersonBuilder().setName("Jan").build();
    return 0;
}

I w końcu nowy sposób nazywany na niektórych stronach Modern Builder Pattern (z operatorem rzutowania):

#include <utility>
#include <string>

class Person {
    friend class PersonBuilder;
private:
    Person() = default;

public:
    std::string name;
};

class PersonBuilder {
public:
    PersonBuilder& setName(const std::string& name) { person.name = name; return *this; }
    operator Person&&() { return std::move(person); }

private:
    Person person;
};

int main(int argc, char *argv[]) {
    Person person = PersonBuilder().setName("Jan");
    return 0;
}

Różnicę widać na pierwszy rzut oka. Zamiast wywoływać metodę build i kopiować/przenosić dane za pomocą zdefiniowanego konstruktora lub konstruktora kopiującego to definiuje się operator rzutowania. Ale czy poza mechanizmem przenoszenia danych jest jeszcze jakaś ZNACZĄCA różnica. Zapis też jest krótszy bo nie używa się dodatkowej metody build() ale osobiście to jestem do niej przyzwyczajony bo przynajmniej wiem, że budowanie się zakończyło.

Co o tym sądzicie? I której składni używacie?

0

Na pewno nie tej:

public:
    std::string name;

2

Dla mnie nie ma różnicy, nie są zbyt ważne.

Nie podoba mi się operator konwersji, bo współcześnie i tak się używa auto, więc wolę:

auto person = PersonBuilder().setName("Jan").build();

a operator konwersji nie współpracuje z auto.
Poza tym nadmiar "magii" (rzeczy, które dzieją się w domyśle), utrudnia czytanie kodu.

Wersja z przenoszeniem ma sens lub nie, w zależności od okoliczności (kwestia zarządzania zasobami). Wada jest taka, że utrudnione jest tworzenie wielu obiektów z tymi samymi ustawieniami.

Z punktu widzenia gcc https://www.godbolt.org/z/66WxdT największe różnice ma wersja 2, a pierwsza i ostatnia różnią się tylko przez wykonanie kopiowania obiektu.

Przykład builder w STL-u std::ostringstream.

1 użytkowników online, w tym zalogowanych: 0, gości: 1