Haruskah serialisasi dan deserialisasi menjadi tanggung jawab kelas yang diserialisasi?

16

Saya saat ini sedang dalam tahap (re) desain beberapa kelas model aplikasi C # .NET. (Model seperti dalam M dari MVC). Kelas model sudah memiliki banyak data yang dirancang dengan baik, perilaku, dan hubungan timbal balik. Saya menulis ulang model dari Python ke C #.

Dalam model Python lama, saya pikir saya melihat kutil. Setiap model tahu bagaimana membuat serial itu sendiri, dan logika serialisasi tidak ada hubungannya dengan perilaku kelas lainnya. Misalnya, bayangkan:

  • Imagekelas dengan suatu .toJPG(String filePath) .fromJPG(String filePath)metode
  • ImageMetaDatakelas dengan a .toString()dan .fromString(String serialized)metode.

Anda dapat membayangkan bagaimana metode serialisasi ini tidak kohesif dengan seluruh kelas, namun hanya kelas yang dapat dijamin untuk mengetahui data yang cukup untuk membuat serial itu sendiri.

Apakah praktik umum bagi kelas untuk mengetahui bagaimana membuat serial dan deserialize itu sendiri? Atau apakah saya melewatkan pola yang sama?

kdbanman
sumber

Jawaban:

16

Saya biasanya menghindari kelas yang tahu bagaimana membuat cerita bersambung sendiri, karena beberapa alasan. Pertama, jika Anda ingin (de) membuat serial ke / dari format yang berbeda, Anda sekarang harus mencemari model dengan logika ekstra itu. Jika model diakses melalui antarmuka, maka Anda juga mencemari kontrak.

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

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

Tetapi bagaimana jika Anda ingin membuat cerita bersambung ke / dari PNG, dan GIF? Sekarang kelas menjadi

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) { ... }
}

Sebagai gantinya, saya biasanya suka menggunakan pola yang mirip dengan yang berikut:

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) { ... }
}

Sekarang, pada titik ini, salah satu peringatan dengan desain ini adalah bahwa serializers perlu mengetahui identityobjek yang diserialisasi. Beberapa orang akan mengatakan bahwa ini adalah desain yang buruk, karena implementasinya bocor di luar kelas. Risiko / imbalan dari ini benar-benar terserah Anda, tetapi Anda dapat sedikit mengubah kelas untuk melakukan sesuatu seperti

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

Ini lebih merupakan contoh umum, karena gambar biasanya memiliki metadata yang sejalan dengannya; hal-hal seperti tingkat kompresi, ruang warna, dll. yang dapat mempersulit proses.

Zymus
sumber
2
Saya akan merekomendasikan serialisasi ke / dari IOStream abstrak atau format biner (teks menjadi jenis format biner tertentu). Dengan cara ini Anda tidak dibatasi untuk menulis ke file. Ingin mengirim data melalui jaringan akan menjadi lokasi keluaran alternatif yang penting.
unholysampler
Poin yang sangat bagus. Saya sedang memikirkan hal itu, tetapi memiliki kentut otak. Saya akan memperbarui kodenya.
Zymus
Saya berasumsi bahwa karena lebih banyak format serialisasi didukung (yaitu lebih banyak implementasi ImageSerializerantarmuka ditulis), ImageSerializerantarmuka juga perlu tumbuh. EX: Format baru mendukung kompresi opsional, yang sebelumnya tidak -> menambah konfigurasi kompresi ke ImageSerializerantarmuka. Tapi kemudian format lain berantakan dengan fitur yang tidak berlaku untuk mereka. Semakin saya memikirkannya, semakin sedikit saya pikir warisan berlaku di sini.
kdbanman
Sementara saya mengerti dari mana Anda berasal, saya merasa itu bukan masalah, karena beberapa alasan. Jika ini adalah format gambar yang ada, kemungkinan serializer sudah tahu cara menangani level kompresi, dan jika itu baru, Anda harus tetap menulisnya. Salah satu solusinya, adalah dengan membebani metode, seperti void serialize(Image image, Stream outputStream, SerializerSettings settings);Lalu itu hanya kasus menghubungkan kompresi dan metadata logika yang ada ke metode baru.
Zymus
3

Serialisasi adalah masalah dua bagian:

  1. Pengetahuan tentang bagaimana membuat struktur kelas alias .
  2. Pengetahuan tentang cara bertahan / mentransfer informasi yang diperlukan untuk membuat instance kelas alias mekanik .

Sejauh mungkin, struktur harus dipisahkan dari mekanika . Ini meningkatkan modularitas sistem Anda. Jika Anda mengubur informasi pada # 2 dalam kelas Anda maka Anda merusak modularitas karena sekarang kelas Anda harus dimodifikasi untuk mengimbangi cara-cara baru serialisasi (jika mereka datang bersama).

Dalam konteks serialisasi gambar, Anda akan menyimpan informasi tentang serialisasi terpisah dari kelas itu sendiri dan menyimpannya dalam algoritma yang dapat menentukan format serialisasi - oleh karena itu, kelas yang berbeda untuk JPEG, PNG, BMP dll. Jika besok baru Algoritma serialisasi datang bersama Anda hanya kode algoritma dan kontrak kelas Anda tetap tidak berubah.

Dalam konteks IPC, Anda dapat membuat kelas Anda terpisah dan kemudian secara selektif menyatakan informasi yang diperlukan untuk serialisasi (dengan anotasi / atribut). Kemudian algoritma serialisasi Anda dapat memutuskan apakah akan menggunakan JSON, Google Protocol Buffer, atau XML untuk serialisasi. Ia bahkan dapat memutuskan apakah akan menggunakan Jackson parser atau parser khusus Anda - ada banyak pilihan yang akan Anda dapatkan dengan mudah ketika Anda mendesain secara modular!

Apoorv Khurasia
sumber
1
Bisakah Anda memberi saya contoh tentang bagaimana kedua hal itu dapat dipisahkan? Saya tidak yakin saya mengerti perbedaannya.
kdbanman