Написание классов данных с шаблоном построителя - PullRequest
1 голос
/ 30 мая 2019

У меня есть класс данных, который использует конструктор для создания объекта и сохраняет данные в буфере в сериализованной форме. Я планирую изменить класс, чтобы добавить и удалить некоторые поля. Существуют системы, которые будут использовать как версию класса для создания данных, то есть текущую версию со всеми полями, так и более новую версию с удаленными / добавленными полями. Я пытаюсь выяснить, каков наилучший способ сделать это, чтобы он был обратно совместим (не нарушая никакого потребителя)?

У меня есть пара предложений о том, как это сделать, но мне трудно выбрать одно из другого.

Требования:
Сохраненные данные должны быть в двоичном формате.
Длина сериализованной записи одинакова в обеих версиях

Существующий код

public class A implements Comparable<A>, Serializable {

private final Buffer buffer;
public static final Builder {
  private Header header//header with version
  private long creationTime;
  private SomeObject someObject;//this is removed in next version
  private OtherObject otherObject;//this is added in next version

  public Builder() { }

 //bunch of getters setters for fields

  public A build() {return new A(this);}

  private A(Builder b) {
   //build the object and put into the buffer
   validate()
  }
  private void validate() {//validates the object}

  public A(Buffer buf) {
   this.buffer=buf;
   validate();
  }
  public A(String encodedString) {
   this(ByteBuffer.wrap(encodedString));
  }
}
// consumers use this to get creationTime for object A
public long getCreationTime() {
 return buffer.getLong(OFFSET_CREATION_DATE);
}
}

Solution1: добавить новые поля в компоновщик и использовать версию в заголовке, чтобы решить, какие поля использовать во время компоновки (в методе компоновки) для создания объекта. Проблема этого подхода заключается в том, что все методы будут существовать во время компиляции для потребителей, и если они не протестируют свой код, каждый объект будет действительным. Поэтому будет сложно определить, какие поля требуются для какой версии во время сборки.

Solution2: Добавьте нового построителя в классе с полями, которые вы хотите. Будут повторяющиеся поля в существующем компоновщике. Потребители могут тогда использовать строителя, которого они хотят. Это кажется чище, потому что строители будут полностью независимы. Проблема этого подхода заключается в том, что, поскольку мы добавляем и удаляем поля, поля будут иметь разные смещения, поэтому получателям придется изменить, чтобы использовать смещение, основанное на versionType. Это также проблематично для будущих версий, потому что тогда у нас будет этот гигантский класс с множеством компоновщиков и логикой в ​​геттерах для каждой версии

Solution3: Создайте новый класс (скажем, B), который расширяет A и имеет свой собственный конструктор. Таким образом, код является более модульным. Проблема в том, что теперь где-то должна быть логика, чтобы различать и знать, какой конструктор вызывать. Например, если потребитель передает base64 для получения объекта A, ему необходимо выяснить, какая это версия.

String encodedString = "someString form of A"
A a = new A(encodedString);

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

1 Ответ

1 голос
/ 30 мая 2019

Aproach 2 в сочетании с aproach 1 + правильное двоичное представление является ответом. Выбрав правильный формат для вашего двоичного представления, проще всего будет выбрать json. Создайте конструкторы бетонов для объектов V1 и V2 и используйте байтовый буфер для их построения. Каждый строитель / завод будет заинтересован только в тех областях, которые он распознает. Вы можете рассмотреть возможность использования поля версии, если сборщик / фабрика пытается десериализовать исключительную версию неправильной версии. Бетонный строитель / завод будет строить только объекты той версии, которую он распознает.

Подклассы, на мой взгляд, не нужны. Вы можете отделить класс Builder / factory от класса объекта. См., Например, «StreamSerializer» из Hazelcast, полностью внешний класс для сущности, предназначенной только для выполнения маршалинга.

Использование правильного формата решит вашу проблему со смещением от Aproach two. Если вам нужно иметь его в двоичном виде, то в качестве обходного пути можно было бы использовать плоский формат, где размер записи больше необходимого, и вы зарезервировали свободное место для изменений. В старые кобольские времена именно так они и делали. Я не рекомендую вам делать это все же. Используй json :) проще всего может быть не самый эффективный. Вы также можете проверить https://developers.google.com/protocol-buffers/ буферы протокола.

В зависимости от того, какой макет вы выберете для сериализации при демаршалинге, вы можете настроить цепочку ответственности, которая пытается десериализовать часть потока. Когда вы отказываетесь от версии, маршалер будет просто удален / деактивирован из цепочки.

...