13 Stimmen

Bauherren in Java versus C++?

In Googles Protokoll Puffer API für Java, verwenden sie diese netten Builder, die ein Objekt erstellen (siehe ici ):

Person john =
  Person.newBuilder()
    .setId(1234)
    .setName("John Doe")
    .setEmail("jdoe@example.com")
    .addPhone(
      Person.PhoneNumber.newBuilder()
        .setNumber("555-4321")
        .setType(Person.PhoneType.HOME))
    .build();

Die entsprechende C++-API verwendet jedoch keine solchen Builders (siehe ici )

Die C++- und die Java-API sollen das Gleiche tun, also frage ich mich, warum man nicht auch in C++ Builder verwendet hat. Gibt es sprachliche Gründe dafür, d.h. ist es nicht idiomatisch oder ist es in C++ verpönt? Oder ist es vielleicht nur die persönliche Vorliebe der Person, die die C++-Version von Protocol Buffers geschrieben hat?

8voto

hrnt Punkte 9582

Die richtige Art, 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 wie folgt verwendet werden, vorausgesetzt, PhoneNumber ist ähnlich definiert:

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

Wenn eine eigene Builder-Klasse gewünscht wird, kann auch das gemacht werden. Solche Builder sollten natürlich natürlich im Stack abgelegt werden.

4voto

philsquared Punkte 22090

Ich würde sagen "nicht idiomatisch", obwohl ich Beispiele für solche fließenden Schnittstellen in C++-Code gesehen habe.

Das kann daran liegen, dass es mehrere Möglichkeiten gibt, das gleiche Problem zu lösen. Normalerweise ist das Problem, das hier gelöst wird, das der benannten Argumente (oder vielmehr deren Fehlen). Eine wohl eher C++-ähnlich Die Lösung für dieses Problem könnte sein Die Parameter-Bibliothek von Boost .

2voto

BD at Rivenhill Punkte 11575

Der Unterschied ist zum Teil idiomatisch, aber auch das Ergebnis einer stärkeren Optimierung der C++-Bibliothek.

Eine Sache, die Sie in Ihrer Frage nicht erwähnt haben, ist, dass die von protoc ausgegebenen Java-Klassen unveränderlich sind und daher Konstruktoren mit (potenziell) sehr langen Argumentlisten und keine Setter-Methoden haben müssen. Das unveränderliche Muster wird in Java häufig verwendet, um die Komplexität im Zusammenhang mit Multi-Threading (auf Kosten der Leistung) zu vermeiden, und das Builder-Muster wird verwendet, um die Mühe zu vermeiden, auf große Konstruktoraufrufe zu schielen und alle Werte an der gleichen Stelle im Code verfügbar zu haben.

Die von protoc ausgegebenen C++-Klassen sind nicht unveränderlich und wurden so konzipiert, dass die Objekte über mehrere Nachrichtenempfänge hinweg wiederverwendet werden können (siehe den Abschnitt "Optimierungstipps" im C++-Grundlagen Seite ); sie sind daher schwieriger und gefährlicher in der Anwendung, aber effizienter.

Sicherlich hätten die beiden Implementierungen im gleichen Stil geschrieben werden können, aber die Entwickler schienen der Meinung zu sein, dass die Benutzerfreundlichkeit für Java und die Leistung für C++ wichtiger ist, was vielleicht die Nutzungsmuster für diese Sprachen bei Google widerspiegelt.

1voto

Rob Kennedy Punkte 158781

Ihre Behauptung, dass "die C++- und die Java-API dasselbe tun sollen", ist unbegründet. Sie sind nicht so dokumentiert, dass sie dasselbe tun. Jede Ausgabesprache kann eine andere Interpretation der in der .proto-Datei beschriebenen Struktur erzeugen. Das hat den Vorteil, dass die Ergebnisse in jeder Sprache idiomatisch sind für diese Sprache . Es minimiert das Gefühl, dass Sie, sagen wir, "Java in C++ schreiben". Das wäre definitiv die Art und Weise Ich würde Es wäre schön, wenn es für jede Nachrichtenklasse eine eigene Builder-Klasse gäbe.

Für ein ganzzahliges Feld foo die C++-Ausgabe von protoc wird eine Methode enthalten void set_foo(int32 value) in der Klasse für die angegebene Nachricht.

Die Java-Ausgabe erzeugt stattdessen zwei Klassen. Die eine stellt direkt die Nachricht dar, hat aber 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 immer noch anders. Die erzeugte Klasse enthält ein Feld, das Sie direkt manipulieren können. Ich gehe davon aus, dass die Plug-ins für C, Haskell und Ruby ebenfalls recht unterschiedlich sind. Solange sie alle eine Struktur darstellen können, die in äquivalente Bits auf der Leitung übersetzt werden kann, haben sie ihre Aufgabe erfüllt. Denken Sie daran, dass es sich um "Protokollpuffer" und nicht um "API-Puffer" handelt.

Der Quellcode für das C++-Plugin wird mit dem protoc Vertrieb. Wenn Sie die Rückgabeart für die set_foo Funktion, können Sie das gerne tun. Normalerweise vermeide ich Antworten, die auf "Es ist Open Source, also kann es jeder ändern" hinauslaufen, 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 sein wird. Das Schwierigste wäre, den Abschnitt des Codes zu finden, der Setter für Felder erzeugt. Sobald Sie diesen gefunden haben, ist die Änderung, die Sie benötigen, wahrscheinlich einfach. Ändern Sie den Rückgabetyp, und fügen Sie ein return *this Anweisung an das Ende des generierten Codes. Sie sollten dann in der Lage sein, Code in der Art zu schreiben, wie er in Hrnt's Antwort .

1voto

moswald Punkte 11212

Um auf meinen Kommentar einzugehen...

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

Dies führt dazu, dass der Code in ungefähr denselben Assembler kompiliert wird wie der entsprechende Code:

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

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

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X