2009-08-25 27 views
10

Estoy tratando de serializar, y yo estoy frente a un problema con una clase abstact.serialización de una clase abstracta

Busqué en Google para una respuesta, y me encontré con this blogitem. Intenté eso y ese trabajo.

Ok, muy agradable. Pero echa un vistazo a los comentarios sobre el tema:

Esta metodología parece estar ocultando el verdadero problema y que es un aplicación inexacta de diseño orientado a objetos patrones, a saber, el patrón de la fábrica.

Tener que cambiar la clase base a referencia cualquier nueva clase de fábrica es contraproducente.

Con un poco después de-pensamiento, el código se puede cambiar a la que cualquier deriva tipo puede estar asociada con la clase abstracta (a través del milagro de interfaces) y no XmlInclude sería necesaria.

Sugiero más investigación sobre patrones de fábrica que parece ser lo que está tratando de implementar aquí.

Lo que se comentarista hablando? Él es un poco vago. ¿Alguien puede explicarlo más en detalle (con un ejemplo)? ¿O solo está diciendo tonterías?

Update (después de leer la primera respuesta)

¿Por qué la commentor hablar de

patrón de la fábrica

y

el código se puede cambiar donde tipo derivado puede estar asociada con la clase abstracta (a través de la milagro de interfaces)

?

es lo que quiere hacer una interfaz, como esto?

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

Respuesta

37

Él es a la vez bueno y lo malo al mismo tiempo.

Con cosas como BinaryFormatter, esto es un no-problema; la corriente en serie contiene metadatos de tipo completo, por lo que si usted tiene:

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

y serializar obj, a continuación, se incluye "Soy un SomeConcrete" en la corriente. Esto hace la vida simple, pero es prolija, especialmente cuando se repite. También es frágil, ya que exige la misma implementación cuando se deserializa; mal para las diferentes implementaciones de cliente/servidor o para el almacenamiento a largo plazo.

Con XmlSerializer (que supongo que el blog está hablando), no hay metadatos, pero los nombres de los elementos (o los atributos xsi:type) se utilizan para ayudar a identificar cuál se utiliza. Para que esto funcione, el serializador necesita saber de antemano qué nombres se asignan a qué tipos.

La forma más sencilla de hacerlo es decorar la clase base con las subclases que conocemos. El serializador puede inspeccionar cada uno de estos (y cualquier atributo adicional específico de xml) para descubrir que cuando ve un elemento <someConcreteType>, se asigna a una instancia SomeConcrete (tenga en cuenta que los nombres no necesitan coincidir, por lo que puede ' solo lo busco por nombre).

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

Sin embargo, si él es un purista (o los datos no está disponible), entonces no es una alternativa; puede especificar todos estos datos por separado a través del constructor sobrecargado al XmlSerializer. Por ejemplo, puede buscar el conjunto de subtipos conocidos de la configuración (o tal vez un contenedor IoC) y configurar el constructor manualmente. Esto no es muy complicado, pero es lo suficientemente complicado como para que no valga la pena a menos que realmente lo necesite.

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

Además, con XmlSerializer si vas a la ruta ctor personalizada, es importante para almacenar en caché y volver a utilizar la instancia XmlSerializer; de lo contrario, se carga un nuevo ensamblaje dinámico por uso, muy caro (no se pueden descargar). Si usa el constructor simple, almacena en caché y reutiliza el modelo, por lo que solo se utiliza un único modelo.

YAGNI dicta que debemos elegir la opción más simple; El uso de [XmlInclude] elimina la necesidad de un constructor complejo y elimina la necesidad de preocuparse por el almacenamiento en caché del serializador. La otra opción está ahí y es totalmente compatible, sin embargo.


Re sus preguntas de seguimiento:

por "patrón de la fábrica", que está hablando el caso en que su código no sabe nada deSomeConcrete, tal vez debido a la COI/DI o marcos similares ; lo que podría tener:

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

cual da cuenta de la aplicación concreta SomeBase adecuada, crea una instancia y lo devuelve. Obviamente, si nuestro código no conoce los tipos concretos (porque solo están especificados en un archivo de configuración), entonces no podemos usar XmlInclude; pero puede analizar los datos de configuración y usar el enfoque ctor (como se indicó anteriormente). En realidad, la mayoría de las veces XmlSerializer se usa con entidades POCO/DTO, por lo que esta es una preocupación artificial.

Y re interfaces; Lo mismo, pero más flexible (una interfaz no exige una jerarquía de tipos). Pero XmlSerializer no es compatible con este modelo. Francamente, duro; ese no es su trabajo. Su trabajo es permitirle almacenar y transportar datos. No implementación. Cualquier entidad generada en el esquema xml no tendrá con los métodos. Los datos son concretos, no abstractos. Mientras piense "DTO", el debate de interfaz no es un problema. Las personas que se sienten molestas por no poder utilizar interfaces en su frontera no han adoptado la separación de las preocupaciones, es decir,que están tratando de hacer:

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

en lugar de la menos restrictiva

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

Ahora, en muchos (la mayoría?) de los casos el DTO y entidades pueden ser el mismo; pero si intenta hacer algo que no le gusta al transporte, presente un DTO; no luches contra el serializador. La misma lógica se aplica cuando las personas están luchando para escribir su objeto:

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

como XML de la forma:

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

Si desea que esta, intoduce un DTO que corresponde al transporte, y mapas entre su entidad y el 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;} 
} 

Esto también se aplica a todas esas otras cosillas como:

  • ¿por qué necesito un constructor sin parámetros?
  • ¿por qué necesito un colocador para las propiedades de mi colección?
  • ¿por qué no puedo usar un tipo inmutable?
  • ¿por qué mi tipo debe ser público?
  • ¿cómo manejo versiones complejas?
  • ¿Cómo manejo diferentes clientes con diferentes diseños de datos?
  • ¿por qué no puedo usar interfaces?
  • etc, etc

No siempre tiene estos problemas; pero si lo hace, introduzca un DTO (o varios) y sus problemas desaparecerán. Retomando esto a la pregunta sobre las interfaces; los tipos DTO pueden no estar basados ​​en interfaz, pero sus tipos de tiempo de ejecución/negocio pueden ser.

+0

gracias por la excelente información. – Natrium

Cuestiones relacionadas