what's the right way to do polymorphism with protocol buffers?

烈酒焚心 提交于 2019-11-27 18:06:37
RickyA

In proto3 the extend keyword has been replaced. From the docs: If you are already familiar with proto2 syntax, the Any type replaces extensions.

syntax = "proto3";

import "google/protobuf/any.proto";

message Foo {
  google.protobuf.Any bar = 1;
}

But beware: Any is essentially a bytes blob. Most of the times it is better to use Oneof:

syntax = "proto3";

message A {
    string a = 1;
}

message B {
    string b = 1;
}

message Foo {
  oneof bar {
    A a = 1;
    B b = 2;
  }
}

There are a few techniques for implementing polymorphism. I try to cover them all here: Protocol Buffer Polymorphism

My preferred approach uses nested extensions:

message Animal
{
    extensions 100 to max;

    enum Type
    {
        Cat = 1;
        Dog = 2;
    }

    required Type type = 1;
}

message Cat
{
    extend Animal
    {
        required Cat animal = 100; // Unique Animal extension number
    }

    // These fields can use the full number range.
    optional bool declawed = 1;
}

message Dog
{
    extend Animal
    {
        required Dog animal = 101; // Unique Animal extension number
    }

    // These fields can use the full number range.
    optional uint32 bones_buried = 1;
}

This is not an anwer to the original question, but it may be helpful for others using v3 of Protocol Buffers. Version 3 disallows the extensions keyword. Running protoc on the following file generates an error with the message Extension ranges are not allowed in proto3.

syntax = "proto3";

message BaseMessage {
  extensions 100 to max;
}

Jon's solution is correct and working but pretty weird (for me). But Protocol Buffers is quite simple, so You can do something like that:

enum Type {
    FOO = 0;
    BAR = 1;
  }

message Foo {
  required Type type = 1;
}

message Bar {
  required Type type = 1;
  required string text = 2;
}

Basically message Bar extends message Foo (from practical side of course). Implementation in Java is simple too:

Bar bar = Bar.newBuilder().setType(Type.BAR).setText("example").build();
byte[] data = bar.toByteArray();

----

Foo foo = Foo.parseFrom(data);
if(foo.getType() == Type.BAR){
   Bar bar = Bar.parseFrom(data);
   System.out.println(bar.getText());
}

I known, it's not an elegant solution, but it's simple and logical.

Check out Extensions and Nested Extensions for a slightly cleaner way to do this.

Have you considered using extensions? You could have your uri field determine the type to use and then just load the appropriate extensions. If you know your fields are mutually exclusive then you could reuse the field id between separate extensions.

You have to handle this all yourself because protocol buffers aren't designed to be self describing beyond a simple list of values. This is touched on in the google techniques page.

A solution a little better, for me, that the @Łukasz Marciniak's answer.

If Bar extends Foo, simply write:

message Bar {
   optional Foo foo = 1;
   optional double aDouble = 2;
}
message Foo {
   optional string aString = 1;
}

So if Foo evolves only Foo message is modified.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!