2009-06-21 19 views

Respuesta

32

Las interfaces de marcador se utilizan para marcar la capacidad de una clase para implementar una interfaz específica en tiempo de ejecución.

El Interface Design y .NET Type Design Guidelines - Interface Design desalentar el uso de interfaces de marcador a favor de la utilización de atributos en C#, pero como señala @Jay Bazuzi, es más fácil para comprobar las interfaces de marcador que para los atributos: o is I

Así que en lugar de esto:

public interface IFooAssignable {} 

public class FooAssignableAttribute : IFooAssignable 
{ 
    ... 
} 

Las directrices .NET recomienda que haga esto:

public class FooAssignableAttribute : Attribute 
{ 
    ... 
} 

[FooAssignable] 
public class Foo 
{  
    ... 
} 
+36

Pautas de diseño de NET, no obstante, es más fácil buscar interfaces de marcador que atributos. 'o soy yo'. –

+21

Además, podemos utilizar completamente los genéricos con interfaces de marcador, pero no con atributos. –

+15

Aunque me encantan los atributos y la forma en que se ven desde un punto de vista declarativo, no son ciudadanos de primera clase en tiempo de ejecución y requieren una cantidad significativa de plomería de nivel relativamente bajo para trabajar. –

6

Una interfaz de marcador es solo una interfaz que está vacía. Una clase implementaría esta interfaz como metadatos para usar por alguna razón. En C#, normalmente usaría atributos para marcar una clase por las mismas razones por las que usaría una interfaz de marcador en otros idiomas.

61

Esto es un poco de una tangente sobre la base de la respuesta de "Mitch trigo".

En general, en cualquier momento veo a la gente citan las directrices de diseño de marco, siempre me gustaría mencionar que:

En general, debería ignorar las directrices de diseño de marco mayor parte del tiempo.

Esto no se debe a ningún problema con las directrices de diseño del marco. Creo que .NET Framework es una fantástica biblioteca de clases. Gran parte de esa fantasía fluye de las pautas de diseño del marco.

Sin embargo, las directrices de diseño no se aplican a la mayoría del código escrito por la mayoría de los programadores. Su propósito es permitir la creación de un gran marco que es utilizado por millones de desarrolladores, no para hacer que la escritura de la biblioteca sea más eficiente.

Muchas de las sugerencias de que puede servir de guía para hacer las cosas que:

  1. puede no ser la forma más sencilla de implementar algo
  2. Puede resultar en la duplicación de código extra
  3. Puede tener supletoria tiempo de ejecución por encima

El .net framework es grande, muy grande. Es tan grande que no sería razonable suponer que alguien tenga un conocimiento detallado de cada aspecto de él. De hecho, es mucho más seguro suponer que la mayoría de los programadores con frecuencia encuentran porciones del marco que nunca antes habían usado.

En ese caso, los objetivos principales de un diseñador de API son:

  1. mantener la coherencia con el resto del marco
  2. eliminar la complejidad innecesaria en el área de la superficie de la API

Las pautas de diseño del marco impulsan a los desarrolladores a crear código que logre esos objetivos.

Eso significa hacer las cosas como evitar capas de la herencia, incluso si esto significa la duplicación de código, o empujar toda excepción tirar código a "puntos de entrada" en lugar de utilizar ayudantes compartidos (para que los seguimientos de pila tienen más sentido en el depurador), y muchas otras cosas similares.

La razón principal por la que esas directrices sugieren el uso de atributos en lugar de interfaces de marcador es porque la eliminación de las interfaces de marcador hace que la estructura de herencia de la biblioteca de clases sea mucho más accesible. Un diagrama de clases con 30 tipos y 6 capas de jerarquía de herencia es muy desalentador en comparación con uno con 15 tipos y 2 capas de jerarquía.

Si realmente hay millones de desarrolladores que usan sus API, o su base de códigos es realmente grande (por ejemplo, más de 100K de código LOC), seguir esas pautas puede ayudar mucho.

Si 5 millones de desarrolladores pasan 15 minutos aprendiendo una API en lugar de pasar 60 minutos aprendiendo, el resultado es un ahorro neto de 428 años-hombre. Eso es mucho tiempo.

La mayoría de los proyectos, sin embargo, no involucran a millones de desarrolladores, o 100K + LOC. En un proyecto típico, con 4 desarrolladores y alrededor de 50K loc, el conjunto de suposiciones es muy diferente. Los desarrolladores del equipo comprenderán mucho mejor cómo funciona el código. Eso significa que tiene mucho más sentido optimizar para producir código de alta calidad rápidamente y para reducir la cantidad de errores y el esfuerzo necesarios para realizar cambios.

Pasar 1 semana el desarrollo de código que es compatible con el marco .NET, frente a 8 horas escribiendo código que es fácil de cambiar y tiene menos errores pueden resultar en:

  1. proyectos tardíos
  2. primas más bajas
  3. Aumento de conteos de errores
  4. Más tiempo en la oficina, y menos tiempo en la playa bebiendo margaritas.

Sin 4999999 otros desarrolladores para absorber los costos, por lo general, no vale la pena.

Por ejemplo, la prueba de interfaces de marcadores se reduce a una sola expresión "es", y da como resultado menos código que busca atributos.

Así que mi consejo es:

  1. Siga las directrices marco religiosamente Si está desarrollando bibliotecas de clases (o widgets UI) destinados para el consumo de amplia difusión.
  2. Considere adoptar algunos de ellos si tiene más de 100 KB de LOC en su proyecto
  3. De lo contrario, ignórelos por completo.
+8

Yo, personalmente, veo cualquier código que escriba como una biblioteca que necesitaré usar más adelante. Realmente no me importa si el consumo es generalizado o no: seguir las pautas aumenta la consistencia y reduce la sorpresa cuando necesito ver mi código y entenderlo años después ... –

+13

No digo que las pautas sean malas. Estoy diciendo que deberían ser diferentes, según el tamaño de tu base de código y la cantidad de usuarios que tengas. Muchas de las pautas de diseño se basan en aspectos como el mantenimiento de la comparabilidad binaria, que no es tan importante para las bibliotecas "internas" utilizadas por algunos proyectos como lo es para BCL. Otras pautas, como las relacionadas con la usabilidad, son casi siempre importantes. La moraleja es no ser excesivamente religioso acerca de las directrices, especialmente en proyectos pequeños. –

+4

+1 - No contesté del todo la pregunta del OP - Propósito de MI - Pero de todos modos fue muy útil. – bzarah

3

Una interfaz de marcador permite etiquetar una clase de forma que se aplique a todas las clases descendientes. Una interfaz de marcador "pura" no definiría ni heredaría nada; un tipo más útil de interfaces de marcador puede ser uno que "hereda" otra interfaz pero no define nuevos miembros. Por ejemplo, si hay una interfaz "IReadableFoo", también se podría definir una interfaz "IImmutableFoo", que se comportaría como un "Foo" pero le prometería a cualquiera que lo use que nada cambiaría su valor. Una rutina que acepta un IImmutableFoo podría usarlo como lo haría con un IReadableFoo, pero la rutina solo aceptaría clases que se declararon como implementadas IImmutableFoo.

No puedo pensar en un montón de usos para las interfaces de marcador "puro". El único en el que puedo pensar sería si EqualityComparer (of T) .Default devolverá Object.Equals para cualquier tipo que implemente IDoNotUseEqualityComparer, incluso si el tipo también implementó IEqualityComparer. Esto permitiría tener un tipo inmóvil abierto sin violar el Principio de Sustitución de Liskov: si el tipo sella todos los métodos relacionados con la prueba de igualdad, un tipo derivado podría agregar campos adicionales y hacerlos mutables, pero la mutación de tales campos no lo haría t ser visible utilizando cualquier método de tipo base. Puede que no sea horrible tener una clase inmutable sin sellar y evitar cualquier uso de EqualityComparer.Default o confiar en las clases derivadas para no implementar IEqualityComparer, pero una clase derivada que implementó IEqualityComparer podría aparecer como una clase mutable incluso cuando se la considere como una base- objeto de clase

18

Como todas las demás respuestas han afirmado que "deben evitarse", sería útil tener una explicación de por qué.

En primer lugar, por qué se utilizan las interfaces de marcador: existen para permitir que el código que está utilizando el objeto que lo implementa compruebe si implementan dicha interfaz y tratan el objeto de manera diferente si lo hace.

El problema con este enfoque es que rompe la encapsulación. El objeto en sí ahora tiene control indirecto sobre cómo se usará externamente. Además, tiene conocimiento del sistema en el que se utilizará. Al aplicar la interfaz de marcador, la definición de clase sugiere que se espera que se use en algún lugar que verifique la existencia del marcador. Tiene conocimiento implícito del entorno en el que se utiliza y está tratando de definir cómo se debe usar. Esto va en contra de la idea de encapsulación porque tiene conocimiento de la implementación de una parte del sistema que existe completamente fuera de su propio alcance.

En un nivel práctico, esto reduce la portabilidad y la reutilización. Si la clase se vuelve a utilizar en una aplicación diferente, también se debe copiar la interfaz y es posible que no tenga ningún significado en el nuevo entorno, por lo que es completamente redundante.

Como tal, el "marcador" es un metadato sobre la clase. Este metadato no es utilizado por la clase en sí y solo tiene sentido para (¡algunos!) El código del cliente externo para que pueda tratar el objeto de cierta manera. Debido a que solo tiene un significado para el código del cliente, los metadatos deben estar en el código del cliente, no en la clase API.

La diferencia entre una "interfaz de marcador" y una interfaz de lo normal es que una interfaz con métodos le dice al mundo exterior como puede ser utilizado mientras que una interfaz vacía implica que está diciendo el mundo exterior como debe ser utilizado .

+1

El objetivo principal de cualquier interfaz es distinguir entre las clases que prometen cumplir el contrato asociado con esa interfaz, y las que no lo hacen. Si bien una interfaz también es responsable de suministrar las firmas de llamadas de los miembros necesarios para cumplir el contrato, es el contrato, en lugar de los miembros, lo que determina si una determinada clase debe implementar una interfaz en particular. Si el contrato para 'IConstructableFromString ' especifica que una clase 'T' solo puede implementar' IConstructableFromString 'si tiene un miembro estático ... – supercat

+0

...' public static T ProduceFromString (String params); ', una clase complementaria a la interfaz puede ofrecer un método 'public static T ProduceFromString (String params) donde T: IConstructableFromString '; si el código del cliente tiene un método como 'T [] MakeManyThings () donde T: IConstructableFromString ', se podrían definir nuevos tipos que podrían funcionar con el código del cliente sin tener que modificar el código del cliente para manejarlos.Si los metadatos estaban en el código del cliente, no sería posible crear nuevos tipos para el uso del cliente existente. – supercat

6

Las interfaces de marcador a veces pueden ser un mal necesario cuando un idioma no admite sindicatos discriminados tipos.

Supongamos que desea definir un método que espera un argumento cuyo tipo debe ser exactamente uno de A, B, o C. En muchos lenguajes funcionales primero (como F#), un tipo de este tipo puede ser definido limpiamente como:

type Arg = 
    | AArg of A 
    | BArg of B 
    | CArg of C 

Sin embargo, en los primeros idiomas de OO como C#, esto no es posible. La única forma de lograr algo similar aquí es definir la interfaz IArg y "marcar" A, B y C con ella.

Por supuesto, puede evitar el uso de la interfaz de marcador simplemente aceptando el tipo "objeto" como argumento, pero luego perderá expresividad y algún grado de seguridad de tipo.

Los tipos de unión discriminada son extremadamente útiles y han existido en los idiomas funcionales durante al menos 30 años. Curiosamente, hasta el día de hoy, todos los lenguajes OO convencionales han ignorado esta característica, aunque en realidad no tiene nada que ver con la programación funcional per se, sino que pertenece al sistema de tipos.

+0

Vale la pena señalar que porque un 'Foo ' tendrá un conjunto separado de campos estáticos para cada tipo 'T', no es difícil tener una clase genérica que contenga campos estáticos que contengan delegados para procesar una' T', y pre-poblar esos campos con funciones para manejar cada tipo con el que se supone que trabaja la clase. El uso de una restricción de interfaz genérica sobre el tipo 'T' comprobaría en el momento del compilador que el tipo suministrado al menos afirmaba ser válido, aunque no podría garantizar que realmente lo era. – supercat

3

Estos dos métodos de extensión va a resolver la mayoría de los problemas de Scott afirma interfaces de marcador a favor sobre atributos:

public static bool HasAttribute<T>(this ICustomAttributeProvider self) 
    where T : Attribute 
{ 
    return self.GetCustomAttributes(true).Any(o => o is T); 
} 

public static bool HasAttribute<T>(this object self) 
    where T : Attribute 
{ 
    return self != null && self.GetType().HasAttribute<T>() 
} 

Ahora usted tienen:

if (o.HasAttribute<FooAssignableAttribute>()) 
{ 
    //... 
} 

frente:

if (o is IFooAssignable) 
{ 
    //... 
} 

I No veo cómo construir una API tardará 5 veces más tiempo con el primer patrón comparado al segundo, como dice Scott.

0

Los marcadores son interfaces vacías. Un marcador está allí o no lo está.

clase Foo: IConfidential

Aquí Mark Foo como confidencial. No se requieren propiedades o atributos adicionales reales.

0

La interfaz de marcador es una interfaz en blanco total que no tiene body/data-members/implementation.
Una clase implementa interfaz de marcador cuando sea necesario, es solo para "marca"; significa que le dice a la JVM que la clase en particular tiene el propósito de clonar, así que permite que se clone. Esta clase particular es Serializar sus objetos así que por favor permita que sus objetos sean serializados.

Cuestiones relacionadas