2010-02-19 40 views
10

Trong Protocol Buffer API của Google cho Java, họ sử dụng những Builders tốt đẹp mà tạo ra một đối tượng (xem here):Nhà xây dựng trong Java so với 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(); 

Nhưng C++ API tương ứng không sử dụng Builders như vậy (xem here)

C++ và Java API có nghĩa vụ phải làm điều tương tự, vì vậy tôi tự hỏi tại sao họ không sử dụng các trình xây dựng trong C++. Có lý do ngôn ngữ nào đằng sau điều đó, tức là nó không phải là thành ngữ hay nó được cau mày trong C++? Hoặc có lẽ chỉ là sở thích cá nhân của người đã viết phiên bản C++ của Protocol Buffers?

+2

Tôi nghĩ rằng đó có thể là sở thích cá nhân của trình cài đặt C++. Các nhà xây dựng không phải (ít nhất là trong kinh nghiệm của tôi), và trên thực tế, tôi sử dụng chúng trên khắp nơi mà một đối tượng có thể có một) nhiều tham số hoặc (nhiều khả năng) b) nhiều tham số tùy chọn. – moswald

+0

một điều bạn không ghi chú trong câu hỏi của bạn là lớp Person là không thay đổi. –

Trả lời

0

Trong C++ bạn phải quản lý rõ ràng bộ nhớ, có thể làm cho thành ngữ trở nên đau đớn hơn - hoặc build() phải gọi hàm hủy cho trình tạo, nếu không bạn phải giữ nó để xóa nó sau khi xây dựng Person vật. Hoặc là một chút đáng sợ với tôi.

+6

Bạn không thể giải quyết vấn đề này bằng cách giữ mọi thứ trên ngăn xếp? – cobbal

+4

hoặc sử dụng con trỏ thông minh (số tiền tương tự, theo một cách) – philsquared

+6

Chỉ cần không đúng - đối tượng tạm thời trong C++ là tầm thường. Chúng bị phá hủy ở cuối biểu thức đầy đủ, mà là sau khi xây dựng. Và với các mẫu, việc tạo các trình xây dựng như vậy sẽ là tầm thường, vì bạn có thể tạo một trình tạo chung - không cần chuyên môn hóa. I E. 'Person = Builder(). (& Person :: id, 1234). (& Person :: Name, "John Doe"); " – MSalters

6

Cách thích hợp để triển khai một cái gì đó tương tự như vậy trong C++ sẽ sử dụng các trình cài đặt trả về tham chiếu đến * này.

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

Lớp có thể được sử dụng như thế này, giả sử PhoneNumber định nghĩa tương tự:

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

Nếu một lớp người xây dựng riêng biệt theo ý muốn, sau đó có thể được thực hiện quá. Các nhà xây dựng như vậy nên được phân bổ trong ngăn xếp, tất nhiên.

+1

Lưu ý rằng điều này đòi hỏi một 'Người' được xây dựng mặc định. Nếu mỗi 'Person' cần một' id', thì không có ctor nào tồn tại. Người xây dựng có thể giải quyết vấn đề bằng cách thu thập các đối số trước khi tạo đối tượng. – MSalters

+0

@MSalters Thật vậy, trong những trường hợp này bạn nên sử dụng cùng một thành ngữ với lớp trình xây dựng (và hàm .build() trả về đối tượng Person có thể kiểm tra tính hợp lệ của đối tượng trước khi xây dựng). – hrnt

+0

Câu trả lời của bạn thiếu một điểm chính mà OP quên đề cập: mã Java đang sử dụng mẫu trình xây dựng ở đây vì lớp Person được định nghĩa là không thay đổi và do đó không có phương thức setter. –

4

Tôi sẽ đi với "không thành ngữ", mặc dù tôi đã thấy ví dụ về các kiểu giao diện thông thạo như vậy trong mã C++.

Có thể do có một số cách để giải quyết cùng một vấn đề cơ bản. Thông thường, vấn đề đang được giải quyết ở đây là của các đối số được đặt tên (hoặc đúng hơn là thiếu của chúng). Một giải pháp có giá trị hơn C++ - như cho sự cố này có thể là Boost's Parameter library.

1

Khiếu nại của bạn rằng "C++ và Java API có nghĩa vụ phải làm điều tương tự" là vô căn cứ. Họ không có tài liệu để làm những điều tương tự. Mỗi ngôn ngữ đầu ra có thể tạo ra một cách giải thích khác nhau về cấu trúc được mô tả trong tệp .proto. Lợi thế của điều đó là những gì bạn nhận được trong mỗi ngôn ngữ là thành ngữ cho ngôn ngữ đó. Nó giảm thiểu cảm giác rằng bạn đang nói "viết Java bằng C++". Điều đó chắc chắn sẽ là cách Tôi muốn cảm thấy nếu có một lớp trình tạo riêng cho mỗi lớp thông báo.

Đối với trường số nguyên foo, đầu ra C++ từ protoc sẽ bao gồm phương thức void set_foo(int32 value) trong lớp cho thư đã cho.

Đầu ra Java thay vào đó sẽ tạo ra hai lớp. Một trực tiếp đại diện cho thông điệp, nhưng chỉ có getters cho lĩnh vực này. Lớp khác là lớp trình xây dựng và chỉ có các trình cài đặt cho trường.

Đầu ra Python vẫn khác. Lớp được tạo sẽ bao gồm một trường mà bạn có thể thao tác trực tiếp. Tôi hy vọng các plug-in cho C, Haskell và Ruby cũng khá khác nhau. Miễn là tất cả chúng có thể đại diện cho một cấu trúc có thể được dịch sang các bit tương đương trên dây, chúng đã hoàn thành công việc của chúng.Hãy nhớ rằng đây là "bộ đệm giao thức", không phải "bộ đệm API".

Nguồn cho trình cắm C++ được cung cấp với phân phối protoc. Nếu bạn muốn thay đổi kiểu trả về cho hàm set_foo, bạn có thể làm như vậy. Tôi thường tránh các câu trả lời có giá trị, "Đó là nguồn mở, vì vậy bất kỳ ai cũng có thể sửa đổi nó" bởi vì nó thường không hữu ích khi đề nghị ai đó học một dự án hoàn toàn mới đủ để thực hiện những thay đổi lớn chỉ để giải quyết vấn đề. Tuy nhiên, tôi không mong đợi nó sẽ rất khó trong trường hợp này. Phần khó nhất là tìm phần mã tạo bộ giải mã cho các trường. Khi bạn thấy điều đó, việc thực hiện thay đổi bạn cần có thể sẽ đơn giản. Thay đổi kiểu trả về và thêm câu lệnh return *this vào cuối mã được tạo. Sau đó, bạn có thể viết mã theo kiểu được đưa ra trong Hrnt's answer.

1

Để theo dõi nhận xét của tôi ...

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

này kết thúc nhận được biên soạn vào khoảng lắp ráp giống như mã tương đương:

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

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

Sự khác biệt là một phần thành ngữ, nhưng là cũng là kết quả của thư viện C++ được tối ưu hóa nhiều hơn.

Một điều bạn không lưu ý trong câu hỏi là các lớp Java do protoc phát ra là không thay đổi và do đó phải có các hàm tạo với danh sách đối số rất dài và không có phương thức setter. Mô hình bất biến được sử dụng phổ biến trong Java để tránh sự phức tạp liên quan đến đa luồng (với chi phí hiệu suất) và mô hình trình xây dựng được sử dụng để tránh đau nheo mắt tại các lời gọi hàm tạo lớn và cần phải có tất cả các giá trị có sẵn tại cùng một điểm trong mã.

Các lớp C++ do protoc phát ra không thay đổi và được thiết kế sao cho các đối tượng có thể được tái sử dụng qua nhiều lần tiếp nhận tin nhắn (xem phần "Mẹo tối ưu hóa" trên C++ Basics Page); do đó chúng khó hơn và nguy hiểm hơn để sử dụng, nhưng hiệu quả hơn. Chắc chắn là trường hợp hai triển khai có thể được viết theo cùng một phong cách, nhưng các nhà phát triển dường như cảm thấy dễ sử dụng quan trọng hơn đối với Java và hiệu suất quan trọng hơn đối với C++, có lẽ phản ánh các mẫu sử dụng cho các ngôn ngữ này tại Google.

Các vấn đề liên quan