Работа с буферами протоколов и внутренними моделями данных

У меня есть существующая внутренняя модель данных для Picture, как показано ниже:

package test.model;
public class Picture {

  private int height, width;
  private Format format;

  public enum Format {
    JPEG, BMP, GIF
  }

  // Constructor, getters and setters, hashCode, equals, toString etc.
}

Теперь я хочу сериализовать его с помощью буферов протокола. Я написал файл Picture.proto, который отражает поля класса Picture и скомпилировал код в пакете test.model.protobuf с именем класса PictureProtoBuf:

package test.model.protobuf;

option java_package = "test.model.protobuf";
option java_outer_classname = "PictureProtoBuf";

message Picture {
  enum Format {
    JPEG = 1;
    BMP = 2;
    GIF = 3;
  }
  required uint32 width = 1;
  required uint32 height = 2;
  required Format format = 3;
}

Теперь я теперь предполагаю, что если у меня есть Picture, который я хочу сериализовать и отправить где-нибудь, мне нужно создать объект PictureProtoBuf и сопоставить все поля, например:

Picture p = new Picture(100, 200, Picture.JPEG);
PictureProtoBuf.Picture.Builder output = PictureProtoBuf.Picture.newBuilder();
output.setHeight(p.getHeight());
output.setWidth(p.getWidth());

Я буду отклеиваться, когда у меня есть перечисление в моей модели данных. Уродливым способом, который я использую сейчас, является:

output.setFormat(PictureProtoBuf.Picture.Format.valueOf(p.getFormat().name());

Однако это подвержено поломке и полагается на совпадение имени перечисления между моей внутренней моделью данных и моделью данных буфера протокола (что не является большим предположением, поскольку имена перечислений в файлах .proto должны быть уникальными). Я могу видеть, что мне приходится иметь дело с операторами switch-craft для перечислений, если вызов .name() из внутренней модели не соответствует имени перечисления, созданному protobuf.

Думаю, мой вопрос в том, правильно ли я это сделаю? Могу ли я отказаться от моей внутренней модели данных (test.model.Picture) в пользу протобуф-сгенерированного (test.model.protobuf.PictureProtoBuf)? Если да, то как я могу реализовать некоторые из тонкостей, которые я сделал в своей внутренней модели данных (например, hashCode(), equals(Object), toString() и т.д.)?

Ответы

Ответ 1

Хотя существующие ответы хороши, я решил пойти немного дальше с предложением Марк Гравелл изучить протозвезды.

Вы можете использовать прототип модуль времени выполнения вместе с динамическим ObjectSchema для создания схем во время выполнения для вашей внутренней модели данных

Мой код теперь сводится к:

// Do this once
private static Schema<Picture> schema = RuntimeSchema.getSchema(Picture.class);
private static final LinkedBuffer buffer = LinkedBuffer.allocate(DEFAULT_BUFFER_SIZE);

// For each Picture you want to serialize...
Picture p = new Picture(100, 200, Picture.JPEG);
byte[] result = ProtobufIOUtil.toByteArray(p, schema, buffer);
buffer.clear();
return result;

Это отличное улучшение по сравнению с библиотекой protobuf Google (см. мой вопрос), когда у вас много и много атрибутов в вашей внутренней модели данных. Существует также ограничение скорости, которое я могу обнаружить (с моими вариантами использования, во всяком случае!)

Ответ 2

Если у вас есть контроль над своей внутренней моделью данных, вы можете изменить test.model.Picture так, чтобы значения перечисления знали свой соответствующий эквивалент protobuf, вероятно, передавая соответствие вашим конструкторам перечислений.

Например, используя Guava BiMap ( двунаправленная карта с уникальными значениями), мы получаем что-то вроде

enum ProtoEnum { // we don't control this
  ENUM1, ENUM2, ENUM3;
}

enum MyEnum {
  ONE(ProtoEnum.ENUM1), TWO(ProtoEnum.ENUM2), THREE(ProtoEnum.ENUM3);

  static final ImmutableBiMap<MyEnum, ProtoEnum> CORRESPONDENCE;

  static {
    ImmutableBiMap.Builder<ProtoEnum, MyEnum> builder = ImmutableBiMap.builder();
    for (MyEnum x : MyEnum.values()) {
      builder.put(x.corresponding, x);
    }
    CORRESPONDENCE = builder.build();
  }

  private final ProtoEnum corresponding;

  private MyEnum(ProtoEnum corresponding) {
    this.corresponding = corresponding;
  }
}

а затем, если мы хотим найти MyEnum, соответствующий ProtoEnum, мы просто делаем MyEnum.CORRESPONDENCE.get(protoEnum), и, чтобы пойти другим путем, мы просто делаем MyEnum.CORRESPONDENCE.inverse().get(myEnum) или myEnum.getCorresponding().

Ответ 3

Один из способов - сохранить только сгенерированное перечисление:

package test.model;
public class Picture {

  private int height, width;
  private PictureProtoBuf.Picture.Format format;

 // Constructor, getters and setters, hashCode, equals, toString etc.
}

Я использовал это несколько раз, это может быть или не иметь смысла в вашем случае. Использование классов, созданных protobuf, в качестве модели данных (или расширения их для добавления функциональности), никогда не рекомендуется.