2009-07-01 14 views
5

Tengo una aplicación que admite varios tipos y versiones de algunos dispositivos. Se puede conectar a estos dispositivos y recuperar diversa información.Crear una clase de propiedades extensibles (OOP)

Dependiendo del tipo de dispositivo, tengo (entre otras cosas) una clase que puede contener varias propiedades. Algunas propiedades son comunes a todos los dispositivos, algunas son exclusivas de un dispositivo en particular.

Estos datos se serializan a xml.

¿Cuál sería una forma preferida de implementar una clase que admitiría futuras propiedades en futuras versiones de estos dispositivos, y también sería compatible con versiones anteriores de la aplicación?

puedo pensar en varias maneras, pero me parece que ninguno de ellos grandes:

  • utilizar una colección de pares nombre-valor:
    • pros: buena compatibilidad con versiones anteriores (tanto xml y versiones anteriores de mi aplicación) y extensibilidad,
    • contras: ningún tipo de seguridad, no intellisense, requiere la implementación de la serialización xml personalizada (para manejar diferentes value objetos)
  • Crear clase derivada propiedades para cada dispositivo nuevo:
    • pros: Tipo de seguridad
    • contras: tener que utilizar XmlInclude o serialización personalizada deserializar clases derivadas, no hacia atrás compatibilidad con el esquema xml previo (aunque implementando serialización personalizada I podría omitir propiedades desconocidas?), requiere conversión para acceder a propiedades en clases derivadas.
  • ¿Otra forma de hacerlo?

Estoy usando C#, por cierto.

+0

¿Se requiere XML? Tus contras dicen que se debe manejar XML. Parece que no quiere usar XML ... – tuergeist

+0

Sí, ese es uno de esos requisitos conocidos por el cliente. :) – Groo

+0

Elegiría "Crear clase de propiedades derivadas para cada nuevo dispositivo" ... – tuergeist

Respuesta

2

¿Qué tal algo similar a un PropertyBag?

+0

Bueno, eso es básicamente una colección de pares nombre-valor, no me importa tanto la implementación interna (una lista, diccionario, lo que sea). Pero tiene los mismos contras. – Groo

+1

Esto es lo que usamos al final, ¡gracias! – Groo

+0

+1 para usar una solución probada y de confianza, bien entendida, aunque no muy sexy :) – MattDavey

0

Creo que la creación de propiedades derivadas es la mejor opción.

Puede diseñar sus nuevas clases utilizando el esquema xml. Y luego simplemente genere el código de clase con xsd.exe.

Con .net no es difícil desarrollar una clase genérica que pueda serializar y deserializar todos los tipos hacia y desde xml.

public static String toXmlString<T>(T value) 
{ 
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); 
    StringWriter stringWriter = new StringWriter(); 
    try { xmlSerializer.Serialize(stringWriter, value); } 
    catch (Exception e) 
    { 
     throw(e); 
    } 
    finally { stringWriter.Dispose(); } 
    String xml = stringWriter.ToString(); 
    stringWriter.Dispose(); 
    return xml; 
} 

public static T fromXmlFile<T>(string fileName, Encoding encoding) 
{ 
    Stream stream; 
    try { stream = File.OpenRead(fileName); } 
    catch (Exception e) 
    { 

     e.Data.Add("File Name", fileName); 
     e.Data.Add("Type", typeof(T).ToString()); 
     throw(e); 
    } 

    BufferedStream bufferedStream = new BufferedStream(stream); 
    XmlSerializer xmlSerializer = new XmlSerializer(typeof(T)); 

    TextReader textReader; 
    if (encoding == null) 
     textReader = new StreamReader(bufferedStream); 
    else 
     textReader = new StreamReader(bufferedStream, encoding); 

    T value; 
    try { value = (T)xmlSerializer.Deserialize(textReader); } 
    catch (Exception e) 
    { 
     e.Data.Add("File Name", fileName); 
     e.Data.Add("Type", typeof(T).ToString()); 
     throw(e); 
    } 
    finally 
    { 
     textReader.Dispose(); 
     bufferedStream.Dispose(); 
    } 
    return value; 
} 
+0

El problema es la compatibilidad con versiones anteriores: no se puede conservar con XmlSerializer, ya que se creó para un tipo específico. Si establece una propiedad en una instancia de una clase derivada, deberá agregar [XmlInclude()] para especificar todas las clases derivadas que XmlSerializer pueda encontrar. – Groo

0

Programatically hablando, esto suena como que podría ser un trabajo para el Decorator Pattern. Básicamente, tienes una súper clase que define una interfaz común para todos estos tipos de dispositivos. Luego tiene clases de decorador que tienen otras propiedades que un dispositivo podría tener. Y, al crear estos dispositivos, puede agregar dinámicamente estas decoraciones para definir nuevas propiedades para el dispositivo.Gráficamente:

alt text

Usted puede mirar en la página de Wikipedia para obtener una descripción más detallada. Después de eso, solo sería cuestión de hacer una serialización para decirle al programa qué decoradores cargar.

1

Si no está limitado a la interoperabilidad con un esquema externo, entonces debe usar Serialización en tiempo de ejecución y SoapFormatter. El patrón de serialización en tiempo de ejecución permite que las clases derivadas especifiquen cuáles de sus propiedades necesitan ser serializadas y qué hacer con ellas cuando se deserializan.

XML Serializer requiere XmlInclude porque, en efecto, necesita definir el esquema que se usará.

+0

Gracias, eso puede funcionar mejor. Pero tendría que ver cuánto tomaría eliminar cosas específicas de XmlSerializer y reemplazarlas con atributos de Serialización en tiempo de ejecución y hacer todos los cambios necesarios. También es interesante porque podía serializar fácilmente tipos inmutables (ahora mismo tengo que implementar IXmlSerializable todo el tiempo y hacerlo a través de la reflexión). – Groo

1

Me gustan los juegos de nombre/valor para este tipo de cosas.

Muchos de sus inconvenientes pueden resolverse: considere una clase base que actúe como un conjunto de nombre/valor general con métodos no operativos para validar pares de nombre/valor entrantes. Para conjuntos conocidos de nombres (es decir, claves), puede crear clases derivadas que implementen métodos de validación.

Por ejemplo, la impresora puede tener una clave conocida "PrintsColor" que solo puede ser "verdadera" o "falsa". Si alguien intenta cargar PrintsColor = "CMYK", su clase de Impresora arrojaría una excepción.

Dependiendo de lo que esté haciendo, puede realizar diferentes formas de hacer la validación más conveniente: métodos de utilidad en la clase base (por ejemplo, checkForValidBoolean()) o una clase base que acepte nombre/tipo información en su constructor para un código más limpio en sus clases derivadas, y tal vez una serialización de XML mayormente automatizada.

Para intellisense: sus clases derivadas podrían tener accesadores básicos que se implementan en términos de búsqueda de claves. Intellisense presentaría esos nombres de acceso.

Este enfoque me ha funcionado bien: hay una especie de miopía en el diseño OO clásico, especialmente para sistemas grandes con componentes enchufados. IMO, la comprobación del tipo de clunkier aquí es muy difícil, pero la flexibilidad hace que valga la pena.

+0

Gracias.Todavía tendría que hacer una serialización personalizada con XmlSerializer, pero al menos obtendría alguna verificación en tiempo de ejecución. Y es compatible con versiones anteriores de todas las aplicaciones, lo que también es una gran cosa. Preferiría tener un montón de clases derivadas de nombre-valor que harían la validación, de lo contrario tendría que poner algún tipo de diccionario para buscar el delegado (predicado) correcto por clave dentro de la clase de propiedades principal (si no lo hice) te pierdas algo aquí?). Y evitaría poner un montón de métodos en la clase principal (marqueForThis, checkForThat). – Groo

0

La idea general de lo que está tratando de lograr aquí es precisamente lo que resuelve el patrón EAV. EAV es un patrón más comúnmente utilizado en el desarrollo de bases de datos, pero el concepto es igualmente válido para las aplicaciones.

Cuestiones relacionadas