Должна ли сериализация и десериализация быть обязанностью сериализуемого класса?

16

В настоящее время я нахожусь на стадии (пере) проектирования нескольких классов моделей приложения на C # .NET. (Модель как в М MVC). Классы моделей уже имеют множество хорошо спроектированных данных, поведений и взаимосвязей. Я переписываю модель с Python на C #.

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

  • Imageкласс с .toJPG(String filePath) .fromJPG(String filePath)методом
  • ImageMetaDataкласс с .toString()и .fromString(String serialized)методом.

Вы можете представить, как эти методы сериализации не связаны с остальным классом, но только класс может гарантировать, что он знает достаточно данных для сериализации.

Является ли обычной практикой для класса знать, как сериализовать и десериализовать себя? Или мне не хватает общей картины?

kdbanman
источник

Ответы:

16

Я вообще избегаю, чтобы класс знал, как сериализовать себя, по нескольким причинам. Во-первых, если вы хотите (де) сериализовать в / из другого формата, теперь вам нужно загрязнить модель этой дополнительной логикой. Если модель доступна через интерфейс, вы также загрязняете контракт.

public class Image
{
    public void toJPG(String filePath) { ... }

    public Image fromJPG(String filePath) { ... }
}

Но что, если вы хотите сериализовать его в / из PNG и GIF? Теперь класс становится

public class Image
{
    public void toJPG(String filePath) { ... }

    public Image fromJPG(String filePath) { ... }

    public void toPNG(String filePath) { ... }

    public Image fromPNG(String filePath) { ... }

    public void toGIF(String filePath) { ... }

    public Image fromGIF(String filePath) { ... }
}

Вместо этого я обычно предпочитаю использовать шаблон, подобный следующему:

public interface ImageSerializer
{
    void serialize(Image src, Stream outputStream);

    Image deserialize(Stream inputStream);
}

public class JPGImageSerializer : ImageSerializer
{
    public void serialize(Image src, Stream outputStream) { ... }

    public Image deserialize(Stream inputStream) { ... }
}

public class PNGImageSerializer : ImageSerializer
{
    public void serialize(Image src, Stream outputStream) { ... }

    public Image deserialize(Stream inputStream) { ... }
}

public class GIFImageSerializer : ImageSerializer
{
    public void serialize(Image src, Stream outputStream) { ... }

    public Image deserialize(Stream inputStream) { ... }
}

Теперь, на этом этапе, одно из предостережений с этим дизайном заключается в том, что сериализаторам необходимо знать identityобъект, который он сериализует. Кто-то скажет, что это плохой дизайн, так как реализация выходит за пределы класса. Риск / награда от этого действительно зависит от вас, но вы можете слегка настроить классы, чтобы сделать что-то вроде

public class Image
{
    public void serializeTo(ImageSerializer serializer, Stream outputStream)
    {
        serializer.serialize(this.pixelData, outputStream);
    }

    public void deserializeFrom(ImageSerializer serializer, Stream inputStream)
    {
        this.pixelData = serializer.deserialize(inputStream);
    }
}

Это более общий пример, поскольку изображения обычно содержат метаданные, которые сопровождают его; такие вещи, как уровень сжатия, цветовое пространство и т. д., которые могут усложнить процесс.

Zymus
источник
2
Я бы порекомендовал сериализовать в / из абстрактного IOStream или двоичного формата (текст является специфическим видом двоичного формата). Таким образом, вы не ограничены записью в файл. Желание отправить данные по сети было бы важным альтернативным выходом.
unholysampler
Очень хороший момент. Я думал об этом, но у меня был пердеть мозг. Я обновлю код.
Зимус
Я предполагаю, что, поскольку поддерживается больше форматов сериализации (т.е. ImageSerializerнаписано больше реализаций интерфейса), ImageSerializerинтерфейс также должен будет расти. Пример: новый формат поддерживает необязательное сжатие, предыдущие не сделали -> добавили возможность настройки сжатия в ImageSerializerинтерфейс. Но тогда другие форматы забиты функциями, которые к ним не относятся. Чем больше я думаю об этом, тем меньше я думаю, что наследование применяется здесь.
kdbanman
Хотя я понимаю, откуда вы, я чувствую, что это не проблема по нескольким причинам. Если это существующий формат изображения, есть вероятность, что сериализатор уже знает, как работать с уровнями сжатия, и если он новый, вам все равно придется его писать. Одним из решений является перегрузка методов, что-то вроде « void serialize(Image image, Stream outputStream, SerializerSettings settings);Тогда» - это всего лишь случай подключения существующей логики сжатия и метаданных к новому методу.
Зимус
3

Сериализация состоит из двух частей:

  1. Знание о том, как создать экземпляр класса ака структуры .
  2. Знание о том, как сохранить / передать информацию, необходимую для создания экземпляра класса, известного как механика .

Насколько это возможно, структура должна быть отделена от механики . Это увеличивает модульность вашей системы. Если вы похороните информацию о # 2 в вашем классе, то вы нарушите модульность, потому что теперь ваш класс должен быть изменен, чтобы идти в ногу с новыми способами сериализации (если они появятся).

В контексте сериализации изображений вы должны хранить информацию о сериализации отдельно от самого класса и хранить ее скорее в алгоритмах, которые могут определять формат сериализации - поэтому разные классы для JPEG, PNG, BMP и т. Д. Если завтра новая Алгоритм сериализации приходит вместе с тем, что вы просто кодируете этот алгоритм и ваш классовый контракт остается неизменным.

В контексте IPC вы можете хранить свой класс отдельно и затем выборочно объявлять информацию, необходимую для сериализации (посредством аннотаций / атрибутов). Тогда ваш алгоритм сериализации может решить, использовать ли JSON, буфер протокола Google или XML для сериализации. Он даже может решить, использовать ли парсер Джексона или свой собственный парсер - есть много опций, которые вы легко получите, когда будете разрабатывать модульно!

Апурв Хурасия
источник
1
Можете ли вы дать мне пример того, как эти две вещи могут быть отделены? Я не уверен, что понимаю разницу.
kdbanman