Подтвердить что ты не робот

Строители в Java по сравнению с С++?

В Google Protocol Buffer API для Java они используют эти красивые Builders, которые создают объект (см. здесь):

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

Но соответствующий С++ API не использует такие Builders (см. здесь)

Предполагается, что С++ и Java API выполняют одно и то же, поэтому мне интересно, почему они также не использовали сборщики на С++. Существуют ли языковые причины этого, т.е. Это не идиоматично, или он нахмурился на С++? Или, возможно, только личное предпочтение человека, написавшего версию протоколов протокола С++?

4b9b3361

Ответ 1

Правильный способ реализовать что-то подобное в С++ будет использовать сеттеры, которые возвращают ссылку на * this.

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

Класс можно использовать так, предполагая аналогично определенный PhoneNumber:

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

Если требуется отдельный класс строителя, это также можно сделать. Такие строители должны быть выделены в стеке, конечно.

Ответ 2

Я бы пошел с "не идиоматическим", хотя я видел примеры таких стилей свободного интерфейса в коде на С++.

Возможно, это связано с тем, что существует множество способов решения одной и той же основной проблемы. Обычно проблема, решаемая здесь, - это именованные аргументы (или, скорее, их отсутствие). Возможно, более С++-подобное решение этой проблемы может быть Библиотека параметров Boost.

Ответ 3

Ваше утверждение о том, что "С++ и Java API должны делать одно и то же", необоснованно. Они не документированы, чтобы делать то же самое. Каждый язык вывода может создавать другую интерпретацию структуры, описанной в файле .proto. Преимущество этого в том, что то, что вы получаете на каждом языке, является идиоматичным для этого языка. Это сводит к минимуму ощущение, что вы, скажем, "пишите Java на С++". Это определенно было бы тем, как я буду чувствовать, если для каждого класса сообщений есть отдельный класс строителя.

Для целочисленного поля foo вывод С++ из protoc будет включать метод void set_foo(int32 value) в классе для данного сообщения.

Выход Java вместо этого генерирует два класса. Один непосредственно представляет сообщение, но имеет только поля для поля. Другой класс - класс строителя и имеет только сеттеры для поля.

Выход Python по-прежнему отличается. Созданный класс будет включать поле, с которым вы можете напрямую манипулировать. Я ожидаю, что плагины для C, Haskell и Ruby также сильно отличаются. Пока они могут представлять структуру, которая может быть переведена на эквивалентные биты на проводе, они выполняют свою работу. Помните, что это "буферы протокола", а не "буферы API".

Источник для плагина С++ предоставляется с распределением протоков. Если вы хотите изменить тип возврата для функции set_foo, вы можете это сделать. Обычно я избегаю ответов, на которые ссылаюсь: "Это с открытым исходным кодом, поэтому любой может его изменить", потому что обычно не рекомендуется рекомендовать, чтобы кто-то учил совершенно новый проект достаточно хорошо, чтобы внести серьезные изменения только для решения проблемы. Однако я не ожидаю, что в этом случае это будет очень сложно. Самая сложная часть - найти раздел кода, который генерирует сеттеры для полей. Как только вы обнаружите, что внесение изменений вам нужно, вероятно, будет простым. Измените тип возвращаемого значения и добавьте оператор return *this в конец сгенерированного кода. Затем вы должны написать код в стиле, указанном в Hrnt answer.

Ответ 4

Чтобы следить за моим комментарием...

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

В результате получается скомпилированный примерно такой же ассемблер, что и эквивалентный код:

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

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

Ответ 5

Разница частично идиоматична, но также является результатом более оптимизированной библиотеки С++.

Одна вещь, которую вы не заметили в своем вопросе, состоит в том, что классы Java, выпущенные protoc, неизменяемы и, следовательно, должны иметь конструкторы с (потенциально) очень длинными списками аргументов и без методов setter. Непрерывный шаблон обычно используется на Java, чтобы избежать сложностей, связанных с многопоточными (за счет производительности), и шаблон строителя используется, чтобы избежать боли прищуривания при больших вызовах конструктора и необходимости иметь все значения, доступные в одном и том же в коде.

Классы С++, выпущенные protoc, не являются неизменяемыми и предназначены для повторного использования объектов несколькими приемами сообщений (см. раздел "Советы по оптимизации" на С++ Basics Страница); они тем сложнее и опаснее использовать, но более эффективны.

Конечно, эти две реализации могли быть написаны в одном стиле, но разработчики, похоже, чувствовали, что простота использования важнее для Java, и производительность была более важной для С++, возможно, отражая шаблоны использования для эти языки в Google.

Ответ 6

В С++ вам нужно явно управлять памятью, что, вероятно, сделало бы идиому более болезненной - либо build() должен вызвать деструктор для строителя, либо вы должны сохранить его, чтобы удалить его после создания Person объект. Для меня это немного страшно.