Różnice w Builder Pattern

Różnice w Builder Pattern
ReallyGrid
  • Rejestracja:około 11 lat
  • Ostatnio:około 2 miesiące
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:

Kopiuj
#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:

Kopiuj
#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):

Kopiuj
#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?

edytowany 5x, ostatnio: ReallyGrid
vpiotr
  • Rejestracja:ponad 13 lat
  • Ostatnio:prawie 3 lata
0

Na pewno nie tej:

Kopiuj
public:
    std::string name;

PK
pan_krewetek
Enkapsulacja służy implementacji. Natomiast co tutaj chciałbyś ukrywać? Informacje? Informacja nie ma implementacji :-)
vpiotr
Enkapsulacja sluzy ukrywaniu zachowan? Ciekawe.
MarekR22
Moderator C/C++
  • Rejestracja:ponad 17 lat
  • Ostatnio:9 minut
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ę:

Kopiuj
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.


Jeśli chcesz pomocy, NIE pisz na priva, ale zadaj dobre pytanie na forum.
edytowany 2x, ostatnio: MarekR22

Zarejestruj się i dołącz do największej społeczności programistów w Polsce.

Otrzymaj wsparcie, dziel się wiedzą i rozwijaj swoje umiejętności z najlepszymi.