La sérialisation et la désérialisation devraient-elles être la responsabilité de la classe en cours de sérialisation?

16

Je suis actuellement dans la phase de (re) conception de plusieurs classes de modèles d'une application C # .NET. (Modèle comme en M de MVC). Les classes de modèle contiennent déjà de nombreuses données, comportements et interrelations bien conçus. Je réécris le modèle de Python en C #.

Dans l'ancien modèle Python, je pense que je vois une verrue. Chaque modèle sait comment se sérialiser et la logique de sérialisation n'a rien à voir avec le reste du comportement de l'une des classes. Par exemple, imaginez:

  • Imageclasse avec une .toJPG(String filePath) .fromJPG(String filePath)méthode
  • ImageMetaDataclasse avec une méthode .toString()et .fromString(String serialized).

Vous pouvez imaginer comment ces méthodes de sérialisation ne sont pas cohérentes avec le reste de la classe, mais seule la classe peut être garantie de connaître suffisamment de données pour se sérialiser.

Est-ce une pratique courante pour une classe de savoir comment se sérialiser et se désérialiser? Ou est-ce que je manque un modèle commun?

kdbanman
la source

Réponses:

16

J'évite généralement que la classe sache comment se sérialiser, pour plusieurs raisons. Tout d'abord, si vous souhaitez (dé) sérialiser vers / à partir d'un format différent, vous devez maintenant polluer le modèle avec cette logique supplémentaire. Si le modèle est accessible via une interface, vous polluez également le contrat.

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

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

Mais que se passe-t-il si vous souhaitez le sérialiser vers / depuis un PNG et un GIF? Maintenant, la classe devient

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

Au lieu de cela, j'aime généralement utiliser un modèle similaire au suivant:

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

Maintenant, à ce stade, l'une des mises en garde avec cette conception est que les sérialiseurs doivent connaître identityl'objet de la sérialisation. Certains diront que c'est une mauvaise conception, car l'implémentation fuit en dehors de la classe. Le risque / récompense de cela dépend vraiment de vous, mais vous pouvez légèrement modifier les cours pour faire quelque chose comme

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

Il s'agit plus d'un exemple général, car les images ont généralement des métadonnées qui vont avec; des choses comme le niveau de compression, l'espace colorimétrique, etc. qui peuvent compliquer le processus.

Zymus
la source
2
Je recommanderais de sérialiser vers / depuis un IOStream abstrait ou le format binaire (le texte étant un type spécifique de format binaire). De cette façon, vous n'êtes pas limité à l'écriture dans un fichier. Vouloir envoyer les données sur le réseau serait un autre emplacement de sortie important.
unholysampler
Très bon point. Je pensais à ça, mais j'avais un pet de cerveau. Je vais mettre à jour le code.
Zymus
Je suppose que plus de formats de sérialisation sont pris en charge (c'est-à-dire que plus d'implémentations de l' ImageSerializerinterface sont écrites), l' ImageSerializerinterface devra également se développer. EX: Un nouveau format prend en charge la compression facultative, les précédents ne le faisaient pas -> ajouter une configurabilité de compression à l' ImageSerializerinterface. Mais alors, les autres formats sont encombrés de fonctionnalités qui ne s'appliquent pas à eux. Plus j'y pense, moins je pense que l'héritage s'applique ici.
kdbanman
Bien que je comprenne d'où vous venez, je pense que ce n'est pas un problème, pour deux raisons. S'il s'agit d'un format d'image existant, il est probable que le sérialiseur sache déjà comment gérer les niveaux de compression, et s'il s'agit d'un nouveau, vous devrez quand même l'écrire. Une solution consiste à surcharger les méthodes, quelque chose comme void serialize(Image image, Stream outputStream, SerializerSettings settings);Ensuite, il s'agit simplement de câbler la logique de compression et de métadonnées existante à la nouvelle méthode.
Zymus
3

La sérialisation est un problème en deux parties:

  1. Connaissances sur la façon d'instancier une classe aka structure .
  2. Connaissances sur la façon de conserver / transférer les informations nécessaires à l'instanciation d'une classe aka mécanique .

Dans la mesure du possible, la structure doit être séparée de la mécanique . Cela augmente la modularité de votre système. Si vous enfouissez les informations sur # 2 dans votre classe, vous rompez la modularité car maintenant votre classe doit être modifiée pour suivre le rythme des nouvelles méthodes de sérialisation (si elles se présentent).

Dans le contexte de la sérialisation d'image, vous devez conserver les informations sur la sérialisation séparément de la classe elle-même et les conserver plutôt dans les algorithmes qui peuvent déterminer le format de sérialisation - par conséquent, différentes classes pour JPEG, PNG, BMP, etc. Si demain un nouveau l'algorithme de sérialisation vient avec vous codez simplement cet algorithme et votre contrat de classe reste inchangé.

Dans le contexte d'IPC, vous pouvez séparer votre classe, puis déclarer sélectivement les informations nécessaires à la sérialisation (par annotations / attributs). Ensuite, votre algorithme de sérialisation peut décider d'utiliser JSON, les tampons de protocole Google ou XML pour la sérialisation. Il peut même décider d'utiliser l'analyseur Jackson ou votre analyseur personnalisé - il existe de nombreuses options que vous obtiendrez facilement lorsque vous concevez de manière modulaire!

Apoorv Khurasia
la source
1
Pouvez-vous me donner un exemple de la façon dont ces deux choses peuvent être découplées? Je ne suis pas sûr de comprendre la distinction.
kdbanman