Sollte die Serialisierung und Deserialisierung in der Verantwortung der zu serialisierenden Klasse liegen?

16

Derzeit bin ich in der (Neu-) Entwurfsphase mehrerer Modellklassen einer C # .NET-Anwendung. (Modell wie in M ​​von MVC). Die Modellklassen verfügen bereits über zahlreiche gut konzipierte Daten, Verhaltensweisen und Zusammenhänge. Ich schreibe das Modell von Python auf C # um.

Im alten Python-Modell sehe ich eine Warze. Jedes Modell kann sich selbst serialisieren, und die Serialisierungslogik hat nichts mit dem übrigen Verhalten einer der Klassen zu tun. Stellen Sie sich zum Beispiel vor:

  • ImageKlasse mit einer .toJPG(String filePath) .fromJPG(String filePath)Methode
  • ImageMetaDataKlasse mit a .toString()und .fromString(String serialized)Methode.

Sie können sich vorstellen, dass diese Serialisierungsmethoden nicht mit dem Rest der Klasse vereinbar sind. Es kann jedoch nur der Klasse garantiert werden, dass sie über ausreichende Daten verfügt, um sich selbst zu serialisieren.

Ist es üblich, dass eine Klasse weiß, wie sie sich selbst serialisiert und deserialisiert? Oder fehlt mir ein gemeinsames Muster?

kdbanman
quelle

Antworten:

16

Ich vermeide es im Allgemeinen aus mehreren Gründen, dass die Klasse weiß, wie sie sich selbst serialisiert. Wenn Sie ein anderes Format (de) serialisieren möchten, müssen Sie das Modell jetzt mit dieser zusätzlichen Logik belasten. Wird auf das Modell über eine Schnittstelle zugegriffen, verschmutzen Sie auch den Vertrag.

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

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

Aber was ist, wenn Sie es zu / von einem PNG und GIF serialisieren möchten? Jetzt wird die Klasse

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

Stattdessen verwende ich normalerweise gerne ein Muster, das dem folgenden ähnelt:

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

Zu diesem Zeitpunkt besteht eine der Einschränkungen bei diesem Entwurf darin, dass die Serialisierer wissen müssen, welches identityObjekt serialisiert wird. Einige würden sagen, dass dies ein schlechtes Design ist, da die Implementierung außerhalb der Klasse leckt. Das Risiko / die Belohnung dafür liegt in der Tat bei Ihnen, aber Sie können die Klassen leicht anpassen, um so etwas wie zu tun

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

Dies ist eher ein allgemeines Beispiel, da Bilder normalerweise Metadaten haben, die damit einhergehen. Dinge wie Komprimierungsgrad, Farbraum usw., die den Prozess erschweren können.

Zymus
quelle
2
Ich würde empfehlen, zu / von einem abstrakten IOStream oder dem Binärformat zu serialisieren (Text ist eine bestimmte Art von Binärformat). Auf diese Weise können Sie nicht nur in eine Datei schreiben. Das Senden der Daten über das Netzwerk wäre ein wichtiger alternativer Ausgabeort.
Unholysampler
Sehr guter Punkt. Ich dachte darüber nach, hatte aber einen Hirnfurz. Ich werde den Code aktualisieren.
Zymus
Ich gehe davon aus, dass mit der Unterstützung weiterer Serialisierungsformate (dh mit der ImageSerializerErstellung weiterer Implementierungen der Schnittstelle) auch die ImageSerializerSchnittstelle wachsen muss. EX: Ein neues Format unterstützt die optionale Komprimierung, die vorherigen Formate haben keine -> zusätzliche Konfigurationsmöglichkeit für die Komprimierung der ImageSerializerBenutzeroberfläche. Aber dann sind die anderen Formate mit Funktionen überfüllt, die für sie nicht zutreffen. Je mehr ich darüber nachdenke, desto weniger denke ich, dass Vererbung hier gilt.
kdbanman
Obwohl ich verstehe, woher Sie kommen, denke ich, dass dies aus mehreren Gründen kein Problem ist. Wenn es sich um ein vorhandenes Bildformat handelt, weiß der Serializer wahrscheinlich bereits, wie er mit Komprimierungsstufen umgeht, und wenn es sich um ein neues handelt, müssen Sie es trotzdem schreiben. Eine Lösung besteht darin, die Methoden zu überladen. void serialize(Image image, Stream outputStream, SerializerSettings settings);Dann müssen nur die vorhandene Komprimierungs- und Metadatenlogik mit der neuen Methode verknüpft werden.
Zymus
3

Die Serialisierung ist ein zweiteiliges Problem:

  1. Kenntnisse darüber, wie eine Klasse oder Struktur instanziiert wird .
  2. Kenntnisse darüber, wie die Informationen, die zum Instanziieren einer Klasse, auch bekannt als Mechanik , benötigt werden, beibehalten / übertragen werden .

Die Struktur sollte so weit wie möglich von der Mechanik getrennt sein . Dies erhöht die Modularität Ihres Systems. Wenn Sie die Informationen zu # 2 in Ihrer Klasse vergraben, brechen Sie die Modularität, da Ihre Klasse jetzt geändert werden muss, um mit den neuen Möglichkeiten der Serialisierung Schritt zu halten (sofern sie verfügbar sind).

Im Zusammenhang mit der Bildserialisierung würden Sie die Informationen zur Serialisierung von der Klasse selbst trennen und sie eher in den Algorithmen aufbewahren, die das Format der Serialisierung bestimmen können - daher verschiedene Klassen für JPEG, PNG, BMP usw. Wenn morgen eine neue Der Serialisierungsalgorithmus wird mitgeliefert. Codieren Sie einfach diesen Algorithmus, und Ihr Klassenvertrag bleibt unverändert.

Im Kontext von IPC können Sie Ihre Klasse getrennt halten und anschließend die für die Serialisierung erforderlichen Informationen selektiv deklarieren (durch Anmerkungen / Attribute). Dann kann Ihr Serialisierungsalgorithmus entscheiden, ob Sie JSON, Google Protocol Buffers oder XML für die Serialisierung verwenden. Es kann sogar entscheiden, ob Sie den Jackson-Parser oder Ihren benutzerdefinierten Parser verwenden möchten - es gibt viele Optionen, die Sie bei modularem Design leicht finden!

Apoorv Khurasia
quelle
1
Können Sie mir ein Beispiel geben, wie diese beiden Dinge entkoppelt werden können? Ich bin mir nicht sicher, ob ich den Unterschied verstehe.
kdbanman