Строители в Java против C ++? - PullRequest
       2

Строители в Java против C ++?

11 голосов
/ 19 февраля 2010

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

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();

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

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

Ответы [ 6 ]

6 голосов
/ 19 февраля 2010

Правильный способ реализовать что-то подобное в C ++ - использовать сеттеры, которые возвращают ссылку на * 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"));

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

4 голосов
/ 19 февраля 2010

Я хотел бы пойти с «не идиоматическим», хотя я видел примеры таких стилей интерфейса свободно в коде C ++.

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

1 голос
/ 04 февраля 2011

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

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

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

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

1 голос
/ 26 февраля 2010

Чтобы прокомментировать мой комментарий ...

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";
1 голос
/ 26 февраля 2010

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

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

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

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

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

0 голосов
/ 19 февраля 2010

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

...