É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.
gracias por la excelente información. – Natrium