Статья состоит из двух частей. В первой части содержится вольный пересказ статьи о статьи о наследовании в protobuf. Вторая часть посвещенна назойливой рекламе самописному «велосипеду» для работы с фреймворком.
Статья не содержит ответа на вопрос «что такое google protocol buffers» и не привязана какому-то конкретному языку програмирования.
Итак, постановка задачи:
- Как имплементировать полиморфизм, при работе с protocol buffers.
- Как искать и находить нужные данные, имея большой файл с сообщениями.
Часть I: Наследование
Предположим, наш протокол содержит три класса объектов: Square, Circle и Polygon. Предположим так же, что все они имеют поле color и поле id. В этом месте у нас появляется несколько причин унаследовать их от общего предка Shape. И, наверное, если бы мы писали на языке с поддержкой полиморфизма, наш код выглядел бы следуюшим образом:
enum Color {
RED,
GREEN,
BLUE
}
struct Point {
int x;
int y;
}
struct Shape {
int id;
Color color;
}
struct Square extends Shape {
Point corner;
int width;
}
struct Circle extends Shape {
Point center;
int radius;
}
struct Polygon extends Shape {
Point [] points;
}
К сожалению, google protocol buffers не поддерживают иерархий. Jon Parise рассматривает три варианта обхода этого ограничения.
Использование опциональных полей
При этом подходе мы создаем отдельную структуру для каждого класса-наследника, а структура Shape содержит опциональные поля на каждый случай.
Такой подход имеет серьезный недосток, нельзя создать новый класс наследник, не поменяв базовый класс. Вслучае, если вы расширяете чужой протокол, это может стать проблеммой.
Кроме того, такой подход позволяет создать никому не известный квадрато-круг, инициализировав оба (square и circle) поля.
enum Color {
RED = 1;
GREEN = 2;
BLUE = 3;
}
message Point {
required fixed32 x = 1;
required fixed32 y = 2;
}
message Square {
required Point corner = 1;
required fixed32 width = 2;
}
message Circle {
required Point center = 1;
required fixed32 radius = 2;
}
message Polygon {
repeated Point points = 1;
}
message Shape {
required TYPE type = 1;
required fixed32 id = 2;
optional Color color = 3;
// наследование
optional Square square = 4;
optional Circle circle = 5;
optional Polygon polygon= 6;
}
Вложеная серилизация
Другой подход подраумевает создание создание структуры Shape с общими для наследников полями, и добавления еще одного поля, где лежат уже сериализованые поля наследника.
Так же не самый удачный вариант, так как «сериализатор» не будет «распаковывать» содержимое поля subclass, и соответственно не будет проведена проверка целостности.
Да и вообще, не красиво как-то.
enum TYPE {
SQUARE = 1;
CIRCLE = 2;
POLYGON = 3;
}
enum Color {
RED = 1;
GREEN = 2;
BLUE = 3;
}
message Point {
required fixed32 x = 1;
required fixed32 y = 2;
}
message Square {
required Point corner = 1;
required fixed32 width = 2;
}
message Circle {
required Point center = 1;
required fixed32 radius = 2;
}
message Polygon {
repeated Point points = 1;
}
message Shape {
required TYPE type = 1;
required fixed32 id = 2;
optional Color color = 3;
// наследование
required bytes subclass = 4;
}
вложеные расширения
Третий (рекомендованый) подход, похож на первый, но вместо опциональных полей используются расширения. Для борьбы с квадрато-кругом заводится поле type.
enum TYPE {
SQUARE = 1;
CIRCLE = 2;
POLYGON = 3;
}
enum Color {
RED = 1;
GREEN = 2;
BLUE = 3;
}
message Point {
required fixed32 x = 1;
required fixed32 y = 2;
}
message Shape {
required TYPE type = 1;
required fixed32 id = 2;
optional Color color = 3;
extensions 4 to max;
}
message Square {
extend Shape {
required Square shape = 5;
}
required Point corner = 1;
required fixed32 width = 2;
}
message Circle {
extend Shape {
required Circle shape = 6;
}
required Point center = 1;
required fixed32 radius = 2;
}
message Polygon {
extend Shape {
required Polygon shape = 7;
}
repeated Point points = 1;
}
Часть II: Поиск
И так, у нас есть файл с описанием структуры сообщений (geom.proto). Наша програма отработала и создала большой файл с самими сообщениям. Хотелось бы находить в нем нужную информацию, а простым текстовым поиском это не всегда возможно.
Например:
- Найдти окружности пересекаюшие оси координат.
- Найдти полигоны с пустым множеством точек
- Найдти полигоны с больше чем 10-ю точками.
Согласитесь, обычный grep нам здесь не поможет.
Конечно несложно, под каждую такую задачу написать маленькую програмку, что бы искала в файле фигуры, с задаными свойствами. Однако, раз уж у нас есть структурированные данные, и есть их формат, то почему бы не написать свой язык запросов?
Вернемся с примерам задач:
type == "CIRCLE" && ((center.x - radius) < 0 || (center.y - radius) < 0))
// Найдти окружности пересекаюшие оси координат.type == "POLYGON" && #points == 0
// Найдти полигоны с пустым множеством точекtype == "POLYGON" && #points > 10
// Найдти полигоны с больше чем 10-ю точками.
Все это, и многое другое умеет делать самописный велосипед.
- Перегонять файлы из бинарного формата в текстовый и наоборот
- Вырезать и распечатывать только определенные поля из сообщений
- Использовать в одном запросе несколько подряд идущих сообщений. Например, найдти все квадраты, идущие сразу за кругами
Надеюсь, что велосипед найдет своего пользователя, а в месте с этим и появится необходимость в подробном топике о синтаксе и возможностях велосипеда.
Спасибо.
Автор: lagranzh