Чи повинні серіалізація та десеріалізація бути відповідальністю класу, який підлягає серіалізації?


16

Наразі я перебуваю на етапі (пере) проектування декількох класів моделей програми C # .NET. (Модель як у M MVC). Модельні класи вже мають безліч добре розроблених даних, поведінки та взаємозв'язків. Я переписую модель з Python на C #.

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

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

Ви можете собі уявити, як ці методи серіалізації не зближуються з рештою класу, але тільки клас може гарантовано знати достатню кількість даних для серіалізації.

Чи загальноприйнята практика для класу знати, як серіалізувати та десяриалізувати себе? Або я пропускаю загальну схему?

Відповіді:


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);
    }
}

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


2
Я рекомендую серіалізувати до / з абстрактного IOStream або двійкового формату (текст є специфічним двійковим форматом). Таким чином, ви не обмежуєтеся записом у файл. Бажаючи надіслати дані через мережу, було б важливим альтернативним місцем виводу.
unholysampler

Дуже хороший момент. Я думав про це, але мав пердеть мозку. Я оновлю код.
Зимус

Я припускаю, що як більше підтримуються формати серіалізації (тобто більше реалізацій ImageSerializerінтерфейсу написано), ImageSerializerінтерфейс також повинен буде рости. EX: Новий формат підтримує необов'язкове стиснення, попередній - не> додає конфігурацію стиснення в ImageSerializerінтерфейс. Але тоді інші формати захаращуються функціями, які не стосуються їх. Чим більше я думаю про це, тим менше я думаю, що спадщина застосовується тут.
kdbanman

Хоча я розумію, звідки ви родом, я відчуваю, що це не проблема, з двох причин. Якщо це вже існуючий формат зображення, швидше за все, серіалізатор вже знає, як впоратися з рівнями стиснення, і якщо це новий, вам доведеться все-таки записати його. Одне рішення - це перевантажити методи, щось подібне. void serialize(Image image, Stream outputStream, SerializerSettings settings);Тоді це лише випадок підключення існуючої логіки стиснення та метаданих до нового методу.
Зимус

3

Серіалізація - це складова з двох частин:

  1. Знання про інстанціювання структури структури ака .
  2. Знання про збереження / передачу інформації, необхідної для інстанції класу, механіки .

Наскільки це можливо, структура повинна зберігатися окремо від механіки . Це збільшує модульність вашої системи. Якщо ви закопуєте інформацію про №2 у своєму класі, то ви порушуєте модульність, оскільки тепер ваш клас повинен бути модифікований, щоб йти в ногу з новими способами серіалізації (якщо вони йдуть разом).

У контексті серіалізації зображення ви б зберігали інформацію про серіалізацію окремо від самого класу та зберігали її, скоріше, в алгоритмах, які можуть визначати формат серіалізації - тому різні класи для JPEG, PNG, BMP тощо. Якщо завтра новий алгоритм серіалізації приходить разом з вами просто кодує цей алгоритм, і ваш класний договір залишається незмінним.

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


1
Чи можете ви надати мені приклад того, як ці дві речі можна роз’єднати? Я не впевнений, що розумію різницю.
kdbanman
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.