在Google的 协议缓冲区 API for Java,他们使用这些很好的构建器来创建一个对象(参见 这里):
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并没有使用这样的构建器(参见 这里)
C ++和Java API应该做同样的事情,所以我想知道他们为什么不在C ++中使用构建器。是否有语言原因,即它不是惯用的,或者在C ++中不受欢迎?或者可能只是编写C ++版协议缓冲区的人的个人偏好?
在C ++中实现类似的东西的正确方法将使用返回* this的引用的setter。
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"));
如果需要单独的构建器类,那么也可以这样做。应该分配这样的建设者
当然,在堆栈中。
虽然我已经在C ++代码中看到了这种流畅的界面风格的例子,但我会选择“非惯用语”。
这可能是因为有很多方法可以解决同样的潜在问题。通常,这里要解决的问题是命名参数(或者更确切地说是缺少参数)。可以说更多 C ++ - 像 解决这个问题的方法可能是 Boost的参数库。
您声称“C ++和Java API应该做同样的事情”是没有根据的。他们没有记录做同样的事情。每种输出语言都可以创建.proto文件中描述的结构的不同解释。这样做的好处是你在每种语言中得到的都是惯用的 对那种语言。它最大限度地减少了你用“用C ++编写Java”的感觉。那绝对是怎么回事 ID 感觉每个消息类是否有单独的构建器类。
对于整数字段 foo
,C ++输出 protoc 将包括一种方法 void set_foo(int32 value)
在给定消息的类中。
而是生成Java输出 二 类。一个直接表示消息,但只有该字段的getter。另一个类是构建器类,只有该字段的setter。
Python输出仍然不同。生成的类将包含可以直接操作的字段。我希望C,Haskell和Ruby的插件也大不相同。只要它们都可以代表一个可以转换为线上等效位的结构,它们就可以完成它们的工作。请记住,这些是“协议缓冲区”,而不是“API缓冲区”。
C ++插件的源代码随附 protoc 分配。如果要更改的返回类型 set_foo
功能,欢迎你这样做。我通常会避免相应的回答,“它是开源的,所以任何人都可以对其进行修改”,因为建议有人学习一个全新的项目,以便为解决问题做出重大改变通常不会有帮助。但是,我不认为在这种情况下会非常困难。最难的部分是找到为字段生成setter的代码部分。一旦找到它,进行所需的更改可能会很简单。更改返回类型,然后添加一个 return *this
语句到生成代码的结尾。然后,您应该能够以给定的样式编写代码 赫恩的回答。
跟进我的评论......
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";
差异部分是惯用的,但也是C ++库更加优化的结果。
您在问题中未注意到的一件事是protoc发出的Java类是不可变的,因此必须具有(可能)非常长的参数列表和没有setter方法的构造函数。不可变模式通常在Java中使用,以避免与多线程相关的复杂性(以性能为代价),并且构建器模式用于避免在大型构造函数调用时眯眼并且需要在同一时间提供所有值的痛苦在代码中指出。
protoc发出的C ++类不是不可变的,其设计使得对象可以在多个消息接收上重用(请参阅“优化提示”部分) C ++基础知识页面);因此它们使用起来更难,更危险,但效率更高。
当然,两个实现可以用相同的样式编写,但是开发人员似乎觉得易用性对于Java来说更重要,性能对于C ++来说更重要,可能反映了这些语言的使用模式。谷歌。
在C ++中,你必须明确地管理内存,这可能会使得习惯用法更加痛苦 - 或者 build()
必须为构建器调用析构函数,否则你必须保留它以在构造之后删除它 Person
目的。
要么对我有点害怕。