una que programacion interfaz interfaces entre diferencia clases clase caracteristicas abstractas abstracta abstracción .net serialization abstract

.net - que - diferencia entre clase abstracta e interfaz



Serialización de una clase abstracta (1)

Estoy intentando serializar, y estoy enfrentando un problema con una clase abstracta.

Busqué en Google una respuesta, y encontré este blog . Intenté eso y ese trabajo.

Ok, muy agradable. Pero mira el comentario sobre el artículo:

Esta metodología parece estar ocultando el verdadero problema y es una implementación incorrecta de los patrones de diseño OO, es decir, el patrón de fábrica.

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

Con un poco de reflexión, el código puede cambiarse para que cualquier tipo derivado pueda asociarse con la clase abstracta (a través del milagro de las interfaces) y no se requerirá XmlInclude.

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

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

Actualización (después de leer la primera respuesta)

¿Por qué el comentarista habla de

patrón de fábrica

y

el código se puede cambiar a donde cualquier tipo derivado se puede asociar con la clase abstracta (a través del milagro de las interfaces)

?

¿Quiere hacer una interfaz como esta?

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


Él es correcto e incorrecto al mismo tiempo.

Con cosas como BinaryFormatter , esto no es un problema; la secuencia serializada contiene metadatos de tipo completo, por lo que si tiene:

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

y serialize obj , luego incluye "I''m a SomeConcrete " en la transmisión. 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 (de lo que supongo que habla el blog), no hay metadatos, pero los nombres de los elementos (o los xsi:type ) se usan para ayudar a identificar cuál se usa. 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 luego inspeccionar cada uno de estos (y cualquier atributo adicional específico de xml) para descubrir que cuando ve un elemento <someConcreteType> , eso se asigna a una instancia SomeConcrete (tenga en cuenta que los nombres no necesitan coincidir, por lo que no puedo simplemente buscarlo 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án disponibles), entonces hay una alternativa; puede especificar todos estos datos por separado a través del constructor sobrecargado en 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 XmlSerializer la ruta de ctor personalizada, es importante almacenar en caché y volver a utilizar la instancia de 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 fábrica", él está hablando sobre el caso donde su código no sabe sobre SomeConcrete , quizás debido a IoC / DI o marcos similares; entonces puedes tener:

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

Lo cual determina la implementación concreta de SomeBase , lo 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 podemos 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 xml-schema no tendrá 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 usar interfaces en su frontera no han adoptado la separación de preocupaciones, es decir, están tratando de hacer:

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

en lugar de lo menos restrictivo

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

Ahora, en muchos (¿la mayoría?) Casos, el DTO y las 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 por 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 esto, introduzca un DTO que corresponda al transporte y haga un mapa 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 quejas como:

  • ¿Por qué necesito un constructor sin parámetros?
  • ¿Por qué necesito un setter 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étera etcétera

No siempre tienes 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.