2010-02-19 22 views
10

In Googles Protocol Buffer API für Java, verwenden sie diese netten Builders, die ein Objekt erstellen (siehe here):Builder in Java gegen C++?

Person john = 
    Person.newBuilder() 
    .setId(1234) 
    .setName("John Doe") 
    .setEmail("[email protected]") 
    .addPhone(
     Person.PhoneNumber.newBuilder() 
     .setNumber("555-4321") 
     .setType(Person.PhoneType.HOME)) 
    .build(); 

Aber der entsprechenden C++ API solche Builders nicht

Die C verwendet (here sehen) ++ und die Java-API sollen dasselbe tun, also frage ich mich, warum sie auch in C++ keine Builder verwendet haben. Gibt es Sprachgründe dafür, d. H. Es ist nicht idiomatisch oder in C++ verpönt? Oder wahrscheinlich nur die persönliche Vorliebe der Person, die die C++ - Version von Protocol Buffers geschrieben hat?

+2

Ich denke, es ist wahrscheinlich die persönliche Präferenz des C++ - Implementierers. Builder sind nicht (in meiner Erfahrung zumindest) in C++ - Code verpönt, und tatsächlich benutze ich sie überall dort, wo ein Objekt a) viele Parameter oder (wahrscheinlicher) b) viele optionale Parameter haben kann. – moswald

+0

Eine Sache, die Sie in Ihrer Frage nicht bemerkt haben, ist, dass die Person-Klasse unveränderlich ist. –

Antwort

0

in C++ explizit Speicher verwalten müssen, was wahrscheinlich das Idiom schmerzhafter machen würde zu verwenden - entweder build() hat den Destruktor für den Bauherrn zu nennen, oder haben Sie es zu halten, um es zu löschen, nachdem die Person Konstruktion Objekt. Entweder ist mir etwas unheimlich.

+6

Könntest du das nicht umgehen, indem du alles auf dem Stapel hältst? – cobbal

+4

oder mit Smartpointern (was in gewisser Weise dasselbe ist) – philsquared

+6

Nur nicht wahr - temporäre Objekte in C++ sind trivial. Sie werden am Ende des vollständigen Ausdrucks, der nach dem Build ist, zerstört. Und mit Templates wäre das Erstellen solcher Builder trivial, da Sie einen generischen Builder erstellen können - ohne Spezialisierung. Ie. 'Person = Erbauer(). (& Person :: id, 1234). (& Person :: Name, "John Doe"); ' – MSalters

6

Der richtige Weg, um so etwas in C++ zu implementieren, würde Setter verwenden, die einen Verweis auf * this zurückgeben.

class Person { 
    std::string name; 
public: 
    Person &setName(string const &s) { name = s; return *this; } 
    Person &addPhone(PhoneNumber const &n); 
}; 

Die Klasse könnte so verwendet werden, in ähnlicher Weise definiert Phone vorausgesetzt:

Person p = Person() 
    .setName("foo") 
    .addPhone(PhoneNumber() 
    .setNumber("123-4567")); 

Wenn eine separate Builder-Klasse gesucht, das kann dann auch getan werden. Solche Builder sollten natürlich im Stack zugewiesen werden.

+1

Beachten Sie, dass hierfür eine standardmäßig erstellte Person erforderlich ist. Wenn jede "Person" eine "ID" benötigt, kann kein solcher existieren. Ein Builder kann das Problem lösen, indem er die Argumente vor dem Erstellen des Objekts sammelt. – MSalters

+0

@MSalters In diesen Fällen sollten Sie das gleiche Idiom mit der Builder-Klasse verwenden (und die Memberfunktion .build(), die das Person-Objekt zurückgibt, könnte die Gültigkeit des Objekts vor der Konstruktion überprüfen). – hrnt

+0

In Ihrer Antwort fehlt ein wichtiger Punkt, den das OP vergessen zu erwähnen: Der Java-Code verwendet hier das Builder-Muster, da die Person-Klasse als unveränderlich definiert ist und daher keine Setter-Methoden hat. –

4

Ich würde mit dem "nicht idiomatischen" gehen, obwohl ich Beispiele für solche fließenden Interface-Stile in C++ Code gesehen habe.

Es kann sein, weil es mehrere Möglichkeiten gibt, das gleiche zugrunde liegende Problem anzugehen. In der Regel wird hier das Problem der benannten Argumente (bzw. deren Fehlen) gelöst. Eine wohl mehr C++ - wie Lösung für dieses Problem könnte Boost's Parameter library sein.

1

Ihre Behauptung, dass "das C++ und die Java-API dasselbe tun sollen", ist unbegründet. Sie sind nicht dokumentiert, um die gleichen Dinge zu tun. Jede Ausgabesprache kann eine andere Interpretation der in der Datei .proto beschriebenen Struktur erzeugen. Der Vorteil davon ist, dass das, was Sie in jeder Sprache bekommen, idiomatisch ist für diese Sprache. Es minimiert das Gefühl, dass Sie beispielsweise "Java in C++ schreiben". Das wäre definitiv, wie I würde fühlen, wenn es eine separate Builder-Klasse für jede Nachrichtenklasse gab.

für eine ganze Zahl Feld foo, die C++ Ausgabe von Protoc wird für die gegebene Nachricht ein Verfahren void set_foo(int32 value) in der Klasse enthalten.

Die Java-Ausgabe generiert stattdessen zwei Klassen. Man stellt die Nachricht direkt dar, hat jedoch nur Getter für das Feld. Die andere Klasse ist die Builder-Klasse und hat nur Setter für das Feld.

Die Python-Ausgabe ist noch anders. Die generierte Klasse enthält ein Feld, das Sie direkt bearbeiten können. Ich erwarte, dass die Plugins für C, Haskell und Ruby auch ganz anders sind. Solange sie alle eine Struktur darstellen können, die in äquivalente Bits auf der Leitung übersetzt werden kann, sind sie ihre Aufgaben erledigt.Denken Sie daran, dass dies "Protokollpuffer" sind, nicht "API-Puffer".

Die Quelle für das C++ - Plug-in wird mit der protoc Verteilung geliefert. Wenn Sie den Rückgabetyp für die Funktion set_foo ändern möchten, können Sie dies gerne tun. Normalerweise meide ich Reaktionen wie "Es ist Open Source, also kann jeder es modifizieren", weil es normalerweise nicht hilfreich ist, jemandem zu empfehlen, ein völlig neues Projekt gut genug zu lernen, um größere Änderungen vorzunehmen, nur um ein Problem zu lösen. Ich erwarte jedoch nicht, dass es in diesem Fall sehr schwer wäre. Der schwierigste Teil wäre, den Codeabschnitt zu finden, der die Setter für die Felder generiert. Sobald Sie das gefunden haben, wird die Änderung, die Sie benötigen, wahrscheinlich einfach sein. Ändern Sie den Rückgabetyp, und fügen Sie am Ende des generierten Codes eine return *this-Anweisung hinzu. Sie sollten dann in der Lage sein, Code in dem Stil zu schreiben, der in Hrnt's answer gegeben wird.

1

Um Follow-up auf meinem Kommentar ...

struct Person 
{ 
    int id; 
    std::string name; 

    struct Builder 
    { 
     int id; 
     std::string name; 
     Builder &setId(int id_) 
     { 
     id = id_; 
     return *this; 
     } 
     Builder &setName(std::string name_) 
     { 
     name = name_; 
     return *this; 
     } 
    }; 

    static Builder build(/* insert mandatory values here */) 
    { 
     return Builder(/* and then use mandatory values here */)/* or here: .setId(val) */; 
    } 

    Person(const Builder &builder) 
     : id(builder.id), name(builder.name) 
    { 
    } 
}; 

void Foo() 
{ 
    Person p = Person::build().setId(2).setName("Derek Jeter"); 
} 

Dieser endet in etwa den gleichen Assembler als Äquivalent-Code kompiliert bekommen:

struct Person 
{ 
    int id; 
    std::string name; 
}; 

Person p; 
p.id = 2; 
p.name = "Derek Jeter"; 
1

Der Unterschied ist zum Teil idiomatischen, ist aber auch das Ergebnis der C++ - Bibliothek wird stärker optimiert.

Eine Sache, die Sie in Ihrer Frage nicht bemerkt haben, ist, dass die Java-Klassen von Protoc unveränderlich sind und daher Konstruktoren mit (möglicherweise) sehr langen Argumentlisten und ohne Setter-Methoden haben müssen. Das unveränderliche Muster wird häufig in Java verwendet, um Komplexität im Zusammenhang mit Multithreading zu vermeiden (auf Kosten der Leistung), und das Builder-Muster wird verwendet, um den Fehler zu vermeiden, bei großen Konstruktoraufrufen zu schielen und alle Werte gleichzeitig verfügbar zu haben zeigen Sie in den Code.

Die vom Protokoll ausgegebenen C++ - Klassen sind nicht unveränderlich und so konzipiert, dass die Objekte über mehrere Nachrichtenempfänge hinweg wiederverwendet werden können (siehe Abschnitt "Optimierungstipps" unter C++ Basics Page); Sie sind daher schwieriger und gefährlicher zu verwenden, aber effizienter.

Es ist sicherlich der Fall, dass die beiden Implementierungen im gleichen Stil geschrieben sein könnten, aber die Entwickler schienen zu glauben, dass die Benutzerfreundlichkeit für Java wichtiger war und die Performance für C++ wichtiger war und möglicherweise die Nutzungsmuster widerspiegelte für diese Sprachen bei Google.