シリアル化と逆シリアル化は、シリアル化されるクラスの責任ですか?


16

現在、C#.NETアプリケーションのいくつかのモデルクラスの(再)設計段階にいます。(MVCのMのようなモデル)。モデルクラスには、適切に設計されたデータ、動作、および相互関係が十分にあります。モデルをPythonからC#に書き換えています。

古いPythonモデルでは、いぼが見えると思います。各モデルはそれ自体をシリアル化する方法を知っており、シリアル化ロジックはクラスの残りの動作とは関係ありません。たとえば、想像してみてください:

  • Image.toJPG(String filePath) .fromJPG(String filePath)メソッドを持つクラス
  • ImageMetaData.toString()and .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) { ... }
}

さて、この時点で、この設計の注意点の1つは、シリアライザーがシリアライズする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インターフェイスも成長する必要があると思います。例:新しい形式ではオプションの圧縮がサポートされていますが、以前の形式ではサポートされていませんでした-> ImageSerializerインターフェイスに圧縮構成可能性が追加されました。しかし、その後、他の形式は、それらに適用されない機能で散らかっています。考えれば考えるほど、ここでの継承の適用は減ると思います。
kdbanman

私はあなたがどこから来たのかを理解していますが、いくつかの理由で問題ではないと感じています。既存の画像形式の場合、シリアライザはすでに圧縮レベルの処理方法を知っている可能性があり、新しいものであれば、とにかくそれを記述する必要があります。1つの解決策は、次のようなメソッドをオーバーロードすることです。void serialize(Image image, Stream outputStream, SerializerSettings settings);それは、既存の圧縮およびメタデータロジックを新しいメソッドに結び付ける場合にすぎません。
ザイムス

3

シリアル化は2つの部分からなる問題です。

  1. クラス別名構造をインスタンス化する方法に関する知識。
  2. メカニックスとも呼ばれるクラスをインスタンス化するために必要な情報を永続化/転送する方法に関する知識。

可能な限り、構造は別に保管されなければならない仕組み。これにより、システムのモジュール性が向上します。クラス内で#2の情報を埋めると、新しいシリアル化の方法に対応するためにクラスを修正する必要があるため、モジュール性が破られます(もしあれば)。

画像のシリアル化のコンテキストでは、クラス自体とは別にシリアル化に関する情報を保持し、シリアル化の形式を決定できるアルゴリズムに保持します。したがって、JPEG、PNG、BMPなどの異なるクラスになります。シリアル化アルゴリズムは、そのアルゴリズムをコーディングするだけで、クラスコントラクトは変更されません。

IPCのコンテキストでは、クラスを分離したままにして、シリアル化に必要な情報を選択的に宣言できます(注釈/属性による)。その後、シリアル化アルゴリズムは、JSON、Google Protocol Buffers、またはXMLをシリアル化に使用するかどうかを決定できます。ジャクソンパーサーを使用するか、カスタムパーサーを使用するかを決定することさえできます。


1
これらの2つのことをどのように分離できるかの例を教えてください。私はその区別を理解しているかどうかわかりません。
kdbanman
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.