2009-08-25 27 views
10

Sto provando a serializzare e sto affrontando un problema con una classe di abstact.Serializzazione di una classe astratta

Ho cercato con google una risposta e ho trovato this blogitem. Ho provato quello e quello lavoro.

Ok, molto bello. Ma controllare il commento della voce:

Questa metodologia sembra nascondere il vero problema e che è un imprecisa attuazione OO progettazione modelli, vale a dire il modello di fabbrica.

Dovendo modificare la classe base su , fare riferimento a qualsiasi nuova classe di fabbrica è autodifesa.

Con un po 'dopo-pensiero, il codice può essere cambiato in cui qualsiasi derivato tipo può essere associato con la classe astratta (attraverso il miracolo della interfacce) e nessuna XmlInclude sarebbe necessaria.

Suggerisco ulteriori ricerche sui modelli di fabbrica che sembrano essere ciò che si sta tentando di implementare qui.

Di cosa sta parlando il commentatore? È piuttosto vago. Qualcuno può spiegarlo più in dettaglio (con un esempio)? O sta solo parlando di sciocchezze?

Update (dopo aver letto la prima risposta)

Perché il commentor parlare

modello di fabbrica

e

il codice può essere modificato a dove qualsiasi.210 tipo derivato può essere associato con la classe astratta (attraverso la miracolo di interfacce)

?

Vuole creare un'interfaccia, come questa?

public interface IWorkaround 
{ 
    void Method(); 
} 

public class SomeBase : IWorkaround 
{ 
    public void Method() 
    { 
     // some logic here 
    } 
} 

public class SomeConcrete : SomeBase, IWorkaround 
{ 
    public new void Method() 
    { 
     base.Method(); 
    } 
} 

risposta

37

Ha ragione e torto allo stesso tempo.

Con cose come BinaryFormatter, questo è un non-problema; il flusso serializzato contiene il tipo di metadati completo, quindi se avete:

[Serializable] abstract class SomeBase {} 
[Serializable] class SomeConcrete : SomeBase {} 
... 
SomeBase obj = new SomeConcrete(); 

e serializzare obj, allora comprende "Sono un SomeConcrete" nel flusso. Questo rende la vita semplice, ma è prolissa, specialmente se ripetuta. È anche fragile, poiché richiede la stessa implementazione quando deserializza; male per le diverse implementazioni client/server o per l'archiviazione a lungo termine.

Con XmlSerializer (di cui credo che il blog stia parlando), non ci sono metadati, ma i nomi degli elementi (o gli attributi xsi:type) vengono utilizzati per identificare l'utilizzo. Affinché questo funzioni, il serializzatore deve sapere in anticipo quali nomi mappano a quali tipi.

Il modo più semplice per farlo è quello di decorare la classe base con le sottoclassi che conosciamo. Il serializzatore può quindi controllare ciascuno di questi (e qualsiasi altro attributo specifico xml) per capire che quando vede un elemento <someConcreteType>, si associa ad un'istanza SomeConcrete (si noti che i nomi non devono necessariamente corrispondere, quindi può ' basta cercarlo per nome).

[XmlInclude(typeof(SomeConcrete))] 
public abstract class SomeBase {} 
public class SomeConcrete : SomeBase {} 
... 
SomeBase obj = new SomeConcrete(); 
XmlSerializer ser = new XmlSerializer(typeof(SomeBase)); 
ser.Serialize(Console.Out, obj); 

Tuttavia, se è un purista (o i dati non sono disponibili), esiste un'alternativa; è possibile specificare tutti questi dati separatamente tramite il costruttore sovraccaricato su XmlSerializer. Ad esempio, è possibile cercare la serie di sottotipi noti dalla configurazione (o forse un contenitore IoC) e impostare manualmente il costruttore. Questo non è molto complicato, ma è abbastanza complicato che non ne valga la pena a meno che non sia effettivamente necessario il .

public abstract class SomeBase { } // no [XmlInclude] 
public class SomeConcrete : SomeBase { } 
... 
SomeBase obj = new SomeConcrete(); 
Type[] extras = {typeof(SomeConcrete)}; // from config 
XmlSerializer ser = new XmlSerializer(typeof(SomeBase), extras); 
ser.Serialize(Console.Out, obj); 

Inoltre, con XmlSerializer se si va via ctor personalizzato, è importante mettere in cache e riutilizzare l'istanza XmlSerializer; in caso contrario viene caricato un nuovo assembly dinamico per utilizzo, molto costoso (non possono essere scaricati). Se si utilizza il semplice costruttore, memorizza nella cache e riutilizza il modello, quindi viene utilizzato solo un singolo modello.

YAGNI dice che dovremmo scegliere l'opzione più semplice; l'utilizzo di [XmlInclude] elimina la necessità di un costruttore complesso e rimuove la necessità di preoccuparsi della memorizzazione nella cache del serializzatore. L'altra opzione è lì ed è completamente supportata, però.


Re tuoi follow-up domande:

per "modello di fabbrica", sta parlando il caso in cui il codice non conosceSomeConcrete, forse a causa di IOC/DI o quadri simili ; così si potrebbe avere:

SomeBase obj = MyFactory.Create(typeof(SomeBase), someArgsMaybe); 

che capisce l'appropriato SomeBase concreta attuazione, crea un'istanza e porge indietro. Ovviamente, se il nostro codice non è a conoscenza dei tipi di calcestruzzo (perché sono specificati solo in un file di configurazione), non possiamo usare XmlInclude; ma noi possiamo analizzare i dati di configurazione e utilizzare l'approccio ctor (come sopra). In realtà, la maggior parte delle volte XmlSerializer viene utilizzata con entità POCO/DTO, quindi questa è una preoccupazione artificiale.

E re interfacce; stessa cosa, ma più flessibile (un'interfaccia non richiede una gerarchia di tipi). Ma XmlSerializer non supporta questo modello. Francamente, duro; non è il suo lavoro. Il suo compito è quello di consentire di memorizzare e trasportare dati. Non implementazione. Qualsiasi entità generata dallo schema xml non avrà metodi. I dati sono concreti, non astratti. Finché pensi "DTO", il dibattito sull'interfaccia è un non-problema. Le persone che sono irritate dal non essere in grado di utilizzare le interfacce sul loro confine non hanno abbracciato la separazione delle preoccupazioni, vale a dire.stanno cercando di fare:

Client runtime entities <---transport---> Server runtime entities 

piuttosto che la meno restrittiva

Client runtime entities <---> Client DTO <--- transport---> 
      Server DTO <---> Server runtime entities 

Ora, in molti (la maggior parte?) dei casi il DTO e le entità possono essere lo stesso; ma se stai cercando di fare qualcosa che il trasporto non piace, presenta un DTO; non combattere il serializzatore. La stessa logica si applica quando le persone stanno lottando per scrivere il loro oggetto:

class Person { 
    public string AddressLine1 {get;set;} 
    public string AddressLine2 {get;set;} 
} 

come XML del modulo:

<person> 
    <address line1="..." line2="..."/> 
</person> 

Se si desidera che questo, intoduce un DTO che corrisponde al trasporto, e della mappa tra l'entità e la DTO:

// (in a different namespace for the DTO stuff) 
[XmlType("person"), XmlRoot("person")] 
public class Person { 
    [XmlElement("address")] 
    public Address Address {get;set;} 
} 
public class Address { 
    [XmlAttribute("line1")] public string Line1 {get;set;} 
    [XmlAttribute("line2")] public string Line2 {get;set;} 
} 

questo vale anche per tutte quelle altre pecche come:

  • perché ho bisogno di un costruttore senza parametri?
  • perché ho bisogno di un setter per le mie proprietà di raccolta?
  • perché non posso usare un tipo immutabile?
  • perché il mio tipo deve essere pubblico?
  • come gestisco il controllo delle versioni complesso?
  • come posso gestire client diversi con layout di dati diversi?
  • perché non posso usare le interfacce?
  • ecc, ecc

Non sempre si dispone di questi problemi; ma se lo fai - presenta un DTO (o più) e i tuoi problemi vanno via. Riportando ciò alla domanda sulle interfacce; i tipi di DTO potrebbero non essere basati su interfaccia, ma i tipi di attività/runtime possono essere.

+0

grazie per l'eccellente informazione. – Natrium

Problemi correlati