La serializzazione e la deserializzazione dovrebbero essere la responsabilità della classe serializzata?


16

Sono attualmente in fase di (ri) progettazione di diverse classi di modelli di un'applicazione C # .NET. (Modello come in M ​​di MVC). Le classi del modello hanno già molti dati, comportamenti e interrelazioni ben progettati. Sto riscrivendo il modello da Python a C #.

Nel vecchio modello Python, penso di vedere una verruca. Ogni modello sa come serializzare se stesso e la logica di serializzazione non ha nulla a che fare con il resto del comportamento di nessuna delle classi. Ad esempio, immagina:

  • Imageclasse con un .toJPG(String filePath) .fromJPG(String filePath)metodo
  • ImageMetaDataclasse con a .toString()e .fromString(String serialized)metodo.

Puoi immaginare come questi metodi di serializzazione non siano coerenti con il resto della classe, tuttavia solo la classe può essere garantita per conoscere dati sufficienti per serializzare se stessa.

È pratica comune per una classe sapere come serializzare e deserializzare se stessa? O mi manca un modello comune?

Risposte:


16

In genere evito che la classe sappia come serializzare se stessa, per un paio di ragioni. Innanzitutto, se si desidera (de) serializzare da / verso un formato diverso, ora è necessario inquinare il modello con quella logica aggiuntiva. Se si accede al modello tramite un'interfaccia, si inquina anche il contratto.

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

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

Ma cosa succede se si desidera serializzare da / verso un PNG e GIF? Ora la classe diventa

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

Invece, in genere mi piace usare un modello simile al seguente:

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

Ora, a questo punto, uno degli avvertimenti con questo design è che i serializzatori devono conoscere identityl'oggetto che sta serializzando. Alcuni direbbero che si tratta di una cattiva progettazione, poiché l'implementazione perde al di fuori della classe. Il rischio / premio di questo dipende davvero da te, ma potresti leggermente modificare le lezioni per fare qualcosa del genere

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

Questo è più un esempio generale, poiché le immagini di solito hanno metadati che lo accompagnano; cose come livello di compressione, spazio colore, ecc. che possono complicare il processo.


2
Consiglio di serializzare da / verso un IOStream astratto o il formato binario (il testo è un tipo specifico di formato binario). In questo modo non sei limitato a scrivere su un file. Volendo inviare i dati attraverso la rete sarebbe un'importante posizione di output alternativa.
unholysampler il

Ottimo punto Ci stavo pensando, ma avevo una scoreggia cerebrale. Aggiornerò il codice.
Zimo

Suppongo che poiché sono supportati più formati di serializzazione (ovvero ImageSerializervengono scritte più implementazioni dell'interfaccia), anche l' ImageSerializerinterfaccia dovrà crescere. ES: un nuovo formato supporta la compressione opzionale, quelli precedenti no -> aggiunge la configurabilità della compressione ImageSerializerall'interfaccia. Ma poi gli altri formati sono ingombri di funzionalità che non si applicano a loro. Più ci penso, meno penso che l'ereditarietà si applichi qui.
kdbanman,

Mentre capisco da dove vieni, sento che non è un problema, per un paio di motivi. Se si tratta di un formato di immagine esistente, è probabile che il serializzatore sappia già come gestire i livelli di compressione, e se è uno nuovo, dovrai comunque scriverlo. Una soluzione è quella di sovraccaricare i metodi, qualcosa del genere void serialize(Image image, Stream outputStream, SerializerSettings settings);Quindi è solo un caso di collegare la logica di compressione e metadati esistente al nuovo metodo.
Zimo

3

La serializzazione è un problema in due parti:

  1. Conoscenza di come creare un'istanza di una struttura aka classe .
  2. Conoscenza di come persistere / trasferire le informazioni necessarie per creare un'istanza di una classe nota anche come meccanica .

Per quanto possibile, la struttura dovrebbe essere mantenuta separata dalla meccanica . Ciò aumenta la modularità del sistema. Se separi le informazioni su # 2 all'interno della tua classe, allora rompi la modularità perché ora la tua classe deve essere modificata per stare al passo con i nuovi modi di serializzazione (se arrivano).

Nel contesto della serializzazione delle immagini manterrai le informazioni sulla serializzazione separate dalla classe stessa e le manterrai piuttosto negli algoritmi che possono determinare il formato della serializzazione; pertanto, classi diverse per JPEG, PNG, BMP ecc. Se domani una nuova L'algoritmo di serializzazione arriva semplicemente codificando tale algoritmo e il contratto di classe rimane invariato.

Nel contesto di IPC, è possibile mantenere la classe separata e quindi dichiarare in modo selettivo le informazioni necessarie per la serializzazione (tramite annotazioni / attributi). Quindi il tuo algoritmo di serializzazione può decidere se utilizzare JSON, buffer di protocollo di Google o XML per la serializzazione. Può anche decidere se utilizzare il parser Jackson o il tuo parser personalizzato: ci sono molte opzioni che potresti ottenere facilmente quando progetti in modo modulare!


1
Puoi darmi un esempio di come queste due cose possono essere disaccoppiate? Non sono sicuro di aver capito la distinzione.
kdbanman,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.