¿Es una mala práctica usar una interfaz solo para la categorización?

12

Por ejemplo:

Decir que tengo clases A, B, C. Tengo dos interfaces, llamémoslas IAnimaly IDog. IDoghereda de IAnimal. Ay Bson IDogs, mientras Cque no lo es, pero es un IAnimal.

La parte importante es que IDogno proporciona funcionalidad adicional. Solo se usa para permitir Ay B, pero no C, para pasar como argumento a ciertos métodos.

¿Es esta una mala práctica?

Kendall Frey
fuente
55
@JarrodRoberson, aunque tiendo a estar de acuerdo, si trabajas en el mundo .Net, estás un poco atascado (o irás en contra de la convención establecida si lo haces de manera diferente).
Daniel B
2
@JarrodRoberson en mi humilde opinión MyProject.Data.IAnimaly MyProject.Data.Animalson mejores que MyProject.Data.Interfaces.Animaly MyProject.Data.Implementations.Animal
Mike Koder
1
El punto es que no debería haber ninguna razón para preocuparse si es un Interfaceo Implementation, ya sea en un prefijo repetitivo o en un espacio de nombres, es una tautología de cualquier manera y viola DRY. Doges todo lo que debería preocuparte. PitBull extends Dogtampoco necesita redundancia de implementación, la palabra extendsme dice todo lo que necesito saber, lea el enlace que publiqué en mi comentario original.
1
@Jarrod: Deja de quejarte conmigo. Quejarse al equipo de desarrollo de .NET. Si fuera en contra de la norma, mis compañeros de desarrollo odiarían mis entrañas.
Kendall Frey

Respuestas:

13

Interfaz que no tiene ningún miembro o tiene exactamente los mismos miembros con otra interfaz que se hereda de la misma interfaz llamada Interfaz de marcador y la está utilizando como marcador.

No es una mala práctica, pero la interfaz puede reemplazarse por atributos (anotaciones) si el idioma que está utilizando lo admite.

Puedo decir con confianza que no es una mala práctica porque he visto un patrón de "interfaz de marcador" de uso intensivo en Orchard Project

Aquí hay una muestra del proyecto Orchard.

public interface ISingletonDependency : IDependency {}

/// <summary>
/// Base interface for services that may *only* be instantiated in a unit of work.
/// This interface is used to guarantee they are not accidentally referenced by a singleton dependency.
/// </summary>
public interface IUnitOfWorkDependency : IDependency {}

/// <summary>
/// Base interface for services that are instantiated per usage.
/// </summary>
public interface ITransientDependency : IDependency {}

Consulte la interfaz de marcador .

Sangre fresca
fuente
¿Los atributos / anotaciones admitirían la verificación en tiempo de compilación (usando C # como lenguaje de referencia)? Sé que podría lanzar una excepción si el objeto no tiene el atributo, pero el patrón de interfaz detecta errores en el momento de la compilación.
Kendall Frey
Si está utilizando Marker Interface, por lo que no tiene el beneficio de verificación de compilación. Básicamente está haciendo una verificación de tipo por interfaz.
Freshblood
Estoy confundido. Este patrón de 'interfaz de marcador' proporciona comprobaciones en tiempo de compilación, en el sentido de que no puede pasar Ca un método que espera un IDog.
Kendall Frey
Si está utilizando este patrón, entonces está haciendo trabajos de reflexión. Quiero decir que estás haciendo dinámica o estáticamente verificaciones de tipo. Estoy seguro de que tiene algo mal en su caso de uso, pero no he visto cómo lo está utilizando.
Freshblood
Permítanme describirlo como lo hice en el comentario sobre la respuesta de Telastyn. Por ejemplo, tengo una Kennelclase que almacena una lista de IDogs.
Kendall Frey
4

Sí, es una mala práctica en casi todos los idiomas.

Los tipos no están allí para codificar datos; son una ayuda para demostrar que sus datos van a donde deben ir y se comportan como deben comportarse. La única razón para subtipar es si cambia un comportamiento polimórfico (y no siempre entonces).

Como no hay que escribir, lo que un IDog puede hacer está implícito. Un programador piensa una cosa, otro piensa otra y las cosas se rompen. O las cosas que representa aumentan a medida que necesita un control más detallado.

[editar: aclaración]

Lo que quiero decir es que estás haciendo algo de lógica basada en la interfaz. ¿Por qué algunos métodos solo funcionan con perros y otros con cualquier animal? Esa diferencia es un contrato implícito ya que no hay un miembro real que proporcione la diferencia; No hay datos reales en el sistema. La única diferencia es la interfaz (de ahí el comentario de 'no escribir'), y lo que eso significa será diferente entre los programadores. [/editar]

Ciertos idiomas proporcionan mecanismos de rasgos donde esto no es tan malo, pero tienden a centrarse en las capacidades, no en el refinamiento. Tengo poca experiencia con ellos, así que no puedo hablar de las mejores prácticas allí. En C ++ / Java / C #, aunque ... no es bueno.

Telastyn
fuente
Estoy confundido por tus dos párrafos del medio. ¿Qué quieres decir con "subtipo"? Estoy usando interfaces, que son contratos, no implementaciones, y no estoy seguro de cómo se aplican sus comentarios. ¿Qué quieres decir con "no escribir"? ¿Qué quieres decir con 'implícito'? Un IDog puede hacer todo lo que un animal IA puede hacer, pero no se garantiza que pueda hacer más.
Kendall Frey
Gracias por la aclaración. En mi caso específico, no estoy usando exactamente las interfaces de manera diferente. Probablemente se pueda describir mejor diciendo que tengo una Kennelclase que almacena una lista de IDogs.
Kendall Frey
@KendallFrey: O estás echando fuera de la interfaz (mal), o estás creando una distinción artificial que huele a una abstracción incorrecta.
Telastyn
2
@Telastyn - El propósito de tal interfaz es proporcionar metadatos
Freshblood
1
@Telastyn: Entonces, ¿cómo se aplican los atributos a la verificación en tiempo de compilación? AFAIK, en C #, los atributos solo se pueden usar en tiempo de ejecución.
Kendall Frey
2

Solo conozco bien C #, por lo que no puedo decir esto para la programación OO en general, pero como ejemplo de C #, si necesita clasificar las clases, las opciones obvias son interfaces, propiedades de enumeración o atributos personalizados. Por lo general, elegiría la interfaz sin importar lo que piensen los demás.

if(animal is IDog)
{
}
if(animal.Type == AnimalType.Dog)
{
}
if(animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
{
}


var dogs1 = animals.OfType<IDog>();
var dogs2 = animals.Where(a => a.Type == AnimalType.Dog);
var dogs3 = animals.Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any());


var dogsFromDb1 = session.Query<IDog>();
var dogsFromDb2 = session.Query<Animal>().Where(a => a.Type == AnimalType.Dog);
var dogsFromDb3 = session.Query<Animal>().Where(a => a.GetType().GetCustomAttributes(typeof(DogAttribute),false).Any());


public void DoSomething(IDog dog)
{
}
public void DoSomething(Animal animal)
{
    if(animal.Type != AnimalType.Dog)
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
public void DoSomething(Animal animal)
{
    if(!animal.GetType().GetCustomAttributes(typeof(DogAttribute), false).Any())
    {
        throw new ArgumentException("This method should be used for dogs only.");
    }
}
Mike Koder
fuente
La última sección del código es principalmente para lo que uso esto. Puedo ver que la versión de la interfaz es obviamente más corta, además de estar comprobada en tiempo de compilación.
Kendall Frey
Tengo un caso de uso similar, pero la mayoría de los desarrolladores de .net desaconsejan firmemente eso, pero no voy a usar atributos
GorillaApe
-1

No hay nada de malo en tener una interfaz que herede otra interfaz sin agregar nuevos miembros, si se espera que todas las implementaciones de la última interfaz puedan hacer cosas útiles que algunas implementaciones de la primera no pueden hacer. De hecho, es un buen patrón que, en mi humilde opinión, debería haberse usado más en cosas como .NET Framework, especialmente porque un implementador cuya clase satisfaría naturalmente las restricciones implícitas en la interfaz más derivada puede indicar que simplemente implementando la interfaz más derivada , sin tener que cambiar ninguno de los códigos o declaraciones de implementación de miembros.

Por ejemplo, supongamos que tengo las interfaces:

interfaz IMaybeMutableFoo {
  Bar Thing {get; set;} // Y también otras propiedades
  bool IsMutable;
  IImmutableFoo AsImmutable ();
}
interfaz IImmutableFoo: IMaybeMutableFoo {};

Se IImmutableFoo.AsImmutable()esperaría que se implementara una implementación de ; IMaybeMutableFoo.AsImmutable()se espera que una implementación de clase mutable devuelva un nuevo objeto profundamente inmutable que contiene una instantánea de los datos. Una rutina que quiere almacenar una instantánea de los datos contenidos en un IMaybeMutableFoopodría ofrecer sobrecargas:

público vacío StoreData (IImmutableFoo ThingToStore)
{
  // Almacenar la referencia será suficiente, ya que se sabe que ThingToStore es inmutable
}
público vacío StoreData (IMaybeMutableFoo ThingToStore)
{
  StoreData (ThingToStore.AsImmutable ()); // Llamar a la sobrecarga más específica
}

En los casos en que el compilador no sabe que lo que se va a almacenar es un IImmutableFoo, llamará AsImmutable(). La función debería volver rápidamente si el elemento ya es inmutable, pero una llamada a la función a través de una interfaz todavía lleva tiempo. Si el compilador sabe que el elemento ya es un IImmutableFoo, puede omitir la llamada de función innecesaria.

Super gato
fuente
Votante: ¿Te gustaría comentar? Hay momentos en que heredar una interfaz sin agregar nada nuevo es una tontería, pero eso no significa que no haya momentos en los que sea una buena técnica (y en mi humilde opinión).
supercat