¿Cuál es el propósito de una interfaz de marcador?
fuente
¿Cuál es el propósito de una interfaz de marcador?
Esto es un poco tangente basado en la respuesta de "Mitch Wheat".
En general, cada vez que veo que la gente cita las pautas de diseño del marco, siempre me gusta mencionar que:
Por lo general, debe ignorar las pautas de diseño del marco la mayor parte del tiempo.
Esto no se debe a ningún problema con las pautas de diseño del marco. Creo que .NET Framework es una biblioteca de clases fantástica. Gran parte de esa fantasía proviene de las pautas de diseño del marco.
Sin embargo, las pautas de diseño no se aplican a la mayoría de los códigos escritos por la mayoría de los programadores. Su propósito es permitir la creación de un gran marco que sea utilizado por millones de desarrolladores, no hacer que la escritura de bibliotecas sea más eficiente.
Muchas de las sugerencias que contiene pueden guiarlo para hacer cosas que:
El framework .net es grande, realmente grande. Es tan grande que sería absolutamente irrazonable suponer que alguien tiene un conocimiento detallado de todos sus aspectos. De hecho, es mucho más seguro asumir que la mayoría de los programadores encuentran con frecuencia partes del marco que nunca antes habían usado.
En ese caso, los objetivos principales de un diseñador de API son:
Las pautas de diseño del marco empujan a los desarrolladores a crear código que cumpla con esos objetivos.
Eso significa hacer cosas como evitar capas de herencia, incluso si eso significa duplicar código, o empujar todas las excepciones arrojando código a "puntos de entrada" en lugar de usar ayudantes compartidos (para que los seguimientos de pila tengan más sentido en el depurador), y mucho de otras cosas similares.
La razón principal por la que esas pautas sugieren el uso de atributos en lugar de interfaces de marcador es porque eliminar 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ódigo es realmente grande (digamos más de 100K LOC), seguir esas pautas puede ayudar mucho.
Si 5 millones de desarrolladores dedican 15 minutos a aprender una API en lugar de dedicar 60 minutos a aprenderla, 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 locomotoras, 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 necesario para realizar cambios.
Pasar 1 semana desarrollando código que sea consistente con el marco .net, en comparación con 8 horas escribiendo código que sea fácil de cambiar y tenga menos errores puede resultar en:
Sin 4.999.999 otros desarrolladores para absorber los costos, generalmente no vale la pena.
Por ejemplo, la prueba de interfaces de marcador se reduce a una sola expresión "es" y da como resultado menos código que la búsqueda de atributos.
Entonces mi consejo es:
virtual protected
método de plantilla enDoSomethingCore
lugar deDoSomething
no es mucho trabajo adicional y usted comunica claramente que es un método de plantilla ... IMNSHO, las personas que escriben aplicaciones sin considerar la API (But.. I'm not a framework developer, I don't care about my API!
) son exactamente aquellas personas que escriben muchos duplicados ( y también código indocumentado y generalmente ilegible), no al revés.Las interfaces de marcador se utilizan para marcar la capacidad de una clase como implementación de una interfaz específica en tiempo de ejecución.
Las Pautas de Diseño de Interfaz y Diseño de Tipo .NET - Diseño de Interfaz desalientan el uso de interfaces de marcador a favor del uso de atributos en C #, pero como señala @Jay Bazuzi, es más fácil verificar las interfaces de marcador que los atributos:
o is I
Entonces en lugar de esto:
public interface IFooAssignable {} public class FooAssignableAttribute : IFooAssignable { ... }
Las pautas de .NET recomiendan que haga esto:
public class FooAssignableAttribute : Attribute { ... } [FooAssignable] public class Foo { ... }
fuente
Dado que todas las demás respuestas han dicho "deberían evitarse", sería útil tener una explicación de por qué.
En primer lugar, por qué se usan las interfaces de marcador: existen para permitir que el código que está usando el objeto que lo implementa compruebe si implementa dicha interfaz y trata el objeto de manera diferente si lo hace.
El problema con este enfoque es que rompe la encapsulación. El objeto en sí tiene ahora control indirecto sobre cómo se utilizará externamente. Además, tiene conocimiento del sistema en el que se va a utilizar. Al aplicar la interfaz del marcador, la definición de clase sugiere que espera ser utilizada en algún lugar que compruebe la existencia del marcador. Tiene un conocimiento implícito del entorno en el que se usa 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.
A nivel práctico, esto reduce la portabilidad y la reutilización. Si la clase se reutiliza en una aplicación diferente, la interfaz también debe copiarse y es posible que no tenga ningún significado en el nuevo entorno, por lo que es completamente redundante.
Como tal, el "marcador" son metadatos sobre la clase. Estos metadatos no son utilizados por la clase en sí y solo son significativos para (¡algunos!) Código de cliente externo para que pueda tratar el objeto de cierta manera. Debido a que solo tiene significado para el código del cliente, los metadatos deben estar en el código del cliente, no en la API de la clase.
La diferencia entre una "interfaz de marcador" y una interfaz normal es que una interfaz con métodos le dice al mundo exterior cómo se puede usar, mientras que una interfaz vacía implica que le dice al mundo exterior cómo debe usarse.
fuente
IConstructableFromString<T>
especifica que una claseT
solo puede implementarseIConstructableFromString<T>
si tiene un miembro estático ...public static T ProduceFromString(String params);
, una clase complementaria a la interfaz puede ofrecer un métodopublic static T ProduceFromString<T>(String params) where T:IConstructableFromString<T>
; si el código del cliente tuviera un método comoT[] MakeManyThings<T>() where T:IConstructableFromString<T>
, 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 tratar con ellos. Si los metadatos estuvieran en el código del cliente, no sería posible crear nuevos tipos para que los utilice el cliente existente.T
y la clase que lo usa esIConstructableFromString<T>
donde tienes un método en la interfaz que describe algún comportamiento, por lo que no es una interfaz de marcador.ProduceFromString
en el ejemplo anterior no implicaría la interfaz de cualquier manera, excepto que la interfaz se usaría como un marcador para indicar qué clases se espera que implementen la función necesaria.Las interfaces de marcador a veces pueden ser un mal necesario cuando un idioma no admite tipos de unión discriminados .
Suponga 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 # ), dicho tipo se puede definir claramente como:
Sin embargo, en los primeros lenguajes 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, podría evitar el uso de la interfaz del marcador simplemente aceptando el tipo "objeto" como argumento, pero entonces perdería expresividad y cierto grado de seguridad de tipos.
Los tipos de sindicatos discriminados son extremadamente útiles y han existido en lenguajes 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 en sí, sino que pertenece al sistema de tipos.
fuente
Foo<T>
que a tendrá un conjunto separado de campos estáticos para cada tipoT
, no es difícil tener una clase genérica que contenga campos estáticos que contengan delegados para procesar aT
, y rellenar previamente esos campos con funciones para manejar todos los tipos de la clase. se supone que debe trabajar. El uso de una restricción de interfaz genérica sobre el tipoT
comprobaría en el momento del compilador que el tipo proporcionado al menos afirmaba ser válido, aunque no podría garantizar que realmente lo fuera.Una interfaz de marcador es solo una interfaz que está vacía. Una clase implementaría esta interfaz como metadatos para ser usados por alguna razón. En C #, usaría más comúnmente atributos para marcar una clase por las mismas razones por las que usaría una interfaz de marcador en otros idiomas.
fuente
Una interfaz de marcador permite etiquetar una clase de una manera que se aplicará 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 prometería a cualquiera que la use que nada cambiaría su valor. Una rutina que acepta un IImmutableFoo podría usarlo como lo haría un IReadableFoo, pero la rutina solo aceptaría clases que fueron declaradas como implementadoras de IImmutableFoo.
No puedo pensar en muchos usos para las interfaces de marcador "puro". El único en el que puedo pensar sería si EqualityComparer (of T) .Default devolvería Object.Equals para cualquier tipo que implementó IDoNotUseEqualityComparer, incluso si el tipo también implementó IEqualityComparer. Esto permitiría tener un tipo inmutable sin sellar 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 hacer que sean 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,
fuente
Estos dos métodos de extensión resolverán la mayoría de los problemas que Scott afirma que favorecen las interfaces de marcador sobre los 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 tu tienes:
if (o.HasAttribute<FooAssignableAttribute>()) { //... }
versus:
if (o is IFooAssignable) { //... }
No veo cómo la construcción de una API llevará 5 veces más tiempo con el primer patrón en comparación con el segundo, como afirma Scott.
fuente
Los marcadores son interfaces vacías. Un marcador está ahí o no lo está.
clase Foo: IConfidential
Aquí marcamos a Foo como confidencial. No se requieren propiedades o atributos adicionales reales.
fuente
La interfaz de marcador es una interfaz en blanco total que no tiene cuerpo / miembros de datos / implementación.
Una clase implementa una interfaz de marcador cuando se requiere, es solo para " marcar "; significa que le dice a la JVM que la clase en particular tiene el propósito de clonar, así que permita que se clone. Esta clase en particular es para serializar sus objetos, así que permita que sus objetos se serialicen.
fuente
La interfaz del marcador es en realidad solo una programación de procedimiento en un lenguaje OO. Una interfaz define un contrato entre implementadores y consumidores, excepto una interfaz de marcador, porque una interfaz de marcador no define nada más que a sí misma. Entonces, desde el principio, la interfaz del marcador falla en el propósito básico de ser una interfaz.
fuente