2011-09-26 13 views
64

Tengo una función que devuelve el mismo tipo de objetos (resultados de la consulta) pero sin propiedades o métodos en común. Para tener un tipo común, utilicé una interfaz vacía como tipo de retorno e implementé eso en ambos.¿El código de las interfaces vacías huele?

Eso no suena bien, por supuesto. Solo puedo consolarme aferrándome a la esperanza de que algún día esas clases tengan algo en común y trasladaré esa lógica común a mi interfaz vacía. Sin embargo, no estoy satisfecho y pensando si debería tener dos métodos diferentes y llamar condicionalmente al siguiente. ¿Sería eso un mejor enfoque?

También me dijeron que .NET Framework usa interfaces vacías para etiquetar.

Mi pregunta es: ¿es una interfaz vacía una fuerte señal de un problema de diseño o es ampliamente utilizada?

EDIT: Para los interesados, descubrí más tarde que las uniones discriminadas en los lenguajes funcionales son la solución perfecta para lo que estaba tratando de lograr. C# aún no parece ser amigable con ese concepto.

EDIT: Escribí un longer piece sobre este tema, explicando el problema y la solución en detalle.

+14

Se llaman [interfaces de marcador] (http://en.wikipedia.org/wiki/Marker_interface_pattern) y aparentemente se usan ampliamente. – BoltClock

+3

Lea esto http://msdn.microsoft.com/en-us/library/ms182128%28v=vs.80%29.aspx (mi opinión debe ser métodos diferentes) – V4Vendetta

+1

Algo en líneas similares http: // stackoverflow. com/questions/835140/using-marker-classes-to-control-logic-flow – V4Vendetta

Respuesta

39

Aunque parece que existe un patrón de diseño (muchos han mencionado "interfaz de marcador" ahora) para ese caso de uso, creo que el uso de tal práctica es una indicación de un olor a código (la mayoría de las veces menos).

Como @ publicada V4Vendetta, hay una regla de análisis estático que se dirige a esto: http://msdn.microsoft.com/en-us/library/ms182128(v=VS.100).aspx

Si el diseño incluye interfaces vacías que se espera que los tipos de implementar, probablemente se está utilizando una interfaz como un marcador o una forma de identificar un grupo de tipos.Si esta identificación ocurrirá en tiempo de ejecución, la forma correcta de lograr esto es usar un atributo personalizado. Utilice la presencia o ausencia del atributo o las propiedades del atributo para identificar los tipos de destino. Si la identificación debe ocurrir en tiempo de compilación, entonces es aceptable usar una interfaz vacía.

Esta es la recomendación de MSDN citado:

eliminar la interfaz o agregar miembros a la misma. Si la interfaz vacía se usa para etiquetar un conjunto de tipos, reemplace la interfaz con un atributo personalizado.

Esto también refleja la sección Crítica del enlace de la wikipedia ya publicado.

Un problema importante con las interfaces de marcador es que una interfaz define un contrato para implementar clases, y ese contrato es heredado por todas las subclases. Esto significa que no puede "poner en práctica" un marcador. En el ejemplo dado, si crea una subclase que no desea serializar (tal vez porque depende del estado transitorio), debe recurrir a tirar explícitamente NotSerializableException (por documentos ObjectOutputStream).

+1

Creo que en mi caso de uso (solo dos clases y es poco probable que se derive), parece estar bien. Ver mi edición de clarificación. –

+0

Estoy aceptando esto como la respuesta. Aunque otras respuestas proporcionan información valiosa también, esta explica más acerca de los posibles problemas con el enfoque. –

+1

Como se ha señalado en otro lugar, aunque los 'atributos' son la forma 'correcta' de hacerlo, son más difíciles de implementar y usar, lo que en la práctica va en contra de su uso. – nicodemus13

6

Respondió su propia pregunta ... "Tengo una función que devuelve objetos completamente diferentes en función de ciertos casos." ... ¿Por qué querría tener la misma función que devuelve objetos completamente diferentes? No veo una razón para que esto sea útil, tal vez usted tenga una buena, en cuyo caso, por favor comparta.

EDIT: Considerando su aclaración, debería utilizar una interfaz de marcador. "completamente diferente" es bastante diferente de "son del mismo tipo". Si fueran completamente diferentes (no solo que no tienen miembros compartidos), eso sería un olor a código.

+0

Esto parece un mejor candidato para un comentario. Agregué una sección de aclaración a mi pregunta. Pregunte si necesita algo más. –

7

Si no se usa como marker interface, diría que sí, que es un olor a código.

Una interfaz define un contrato al que el implementador se adhiere: si tiene interfaces vacías que no usan la reflexión (como hace con las interfaces de marcador), entonces también podría usar Object como (ya existente) tipo de base

+1

'object' sería demasiado genérico y no daría ninguna pista sobre el" tipo "de devolución. La interfaz me ayuda a reducir mis opciones para interpretar el valor de retorno de la función. pero entiendo tu comentario en relación con la falta de aclaración sobre la similitud de los objetos que estoy devolviendo. –

+0

@ssg - bastante. Debe haber algo similar con estos objetos si los pasa a un método para operarlos, de lo contrario debería tener métodos separados. – Oded

9

Usted declara que su función "devuelve objetos completamente diferentes en función de ciertos casos", pero ¿cuán diferentes son? ¿Podría uno ser un escritor de secuencias, otra una clase de UI, otro un objeto de datos? No ... lo dudo!

Puede que sus objetos no tengan ningún método o propiedad común, sin embargo, probablemente sean parecidos en cuanto a su función o uso. En ese caso, un marker interface parece completamente apropiado.

+0

Son diferentes tipos de resultados de consulta pero ambos resultados de consulta, sí. –

+1

OK - como pensé, tienen un rol común, a pesar de que no contienen propiedades comunes. ¡Parece que una interfaz de marcador es perfecta! – ColinE

3

Como probablemente muchos ya han dicho, una interfaz vacía tiene un uso válido como una "interfaz de marcador".

Probablemente el mejor uso que se me ocurre es denotar un objeto como perteneciente a un subconjunto particular del dominio, manejado por un Repositorio correspondiente. Digamos que tiene bases de datos diferentes desde las cuales recupera datos, y tiene una implementación de repositorio para cada una. Un Repositorio en particular solo puede manejar un subconjunto y no se le debe dar una instancia de un objeto de ningún otro subconjunto. Su modelo de dominio podría tener este aspecto:

//Every object in the domain has an identity-sourced Id field 
public interface IDomainObject 
{ 
    long Id{get;} 
} 

//No additional useful information other than this is an object from the user security DB 
public interface ISecurityDomainObject:IDomainObject {} 

//No additional useful information other than this is an object from the Northwind DB 
public interface INorthwindDomainObject:IDomainObject {} 


//No additional useful information other than this is an object from the Southwind DB 
public interface ISouthwindDomainObject:IDomainObject {} 

sus repositorios a continuación, se pueden hacer genérico para ISecurityDomainObject, INorthwindDomainObject y ISouthwindDomainObject, y luego tener un control en tiempo de compilación que su código no está tratando de aprobar una Seguridad objetar al Northwind DB (o cualquier otra permutación). En situaciones como esta, la interfaz proporciona información valiosa sobre la naturaleza de la clase, incluso si no proporciona ningún contrato de implementación.

Cuestiones relacionadas