¿Deberían las interfaces extenderse (y al hacerlo heredar métodos de) otras interfaces

13

Aunque esta es una pregunta general, también es específica de un problema que estoy experimentando actualmente. Actualmente tengo una interfaz especificada en mi solución llamada

public interface IContextProvider
{
   IDataContext { get; set; }
   IAreaContext { get; set; }
}

Esta interfaz se usa a menudo en todo el programa y, por lo tanto, tengo fácil acceso a los objetos que necesito. Sin embargo, en un nivel bastante bajo de una parte de mi programa, necesito acceso a otra clase que usará IAreaContext y realizará algunas operaciones fuera de él. Así que he creado otra interfaz de fábrica para hacer esta creación llamada:

public interface IEventContextFactory 
{
   IEventContext CreateEventContext(int eventId);
}

Tengo una clase que implementa IContextProvider y se inyecta con NinJect. El problema que tengo es que el área donde necesito usar este IEventContextFactory solo tiene acceso al IContextProvider y usa otra clase que necesitará esta nueva interfaz. No quiero tener que instanciar esta implementación de IEventContextFactory en el nivel bajo y prefiero trabajar con la interfaz IEventContextFactory en todo momento. Sin embargo, tampoco quiero tener que inyectar otro parámetro a través de los constructores solo para que pase a la clase que lo necesita, es decir

// example of problem
public class MyClass
{
    public MyClass(IContextProvider context, IEventContextFactory event)
    {
       _context = context;
       _event = event;
    }

    public void DoSomething() 
    {
       // the only place _event is used in the class is to pass it through
       var myClass = new MyChildClass(_event);
       myClass.PerformCalculation();      
    }
}

Entonces, mi pregunta principal es, ¿sería esto aceptable o es incluso una práctica común o buena hacer algo como esto (la interfaz se extiende a otra interfaz):

public interface IContextProvider : IEventContextFactory

o debería considerar mejores alternativas para lograr lo que necesito. Si no he proporcionado suficiente información para dar sugerencias, hágamelo saber y puedo proporcionar más.

dreza
fuente
2
Reformularía el título de su pregunta como "¿Deben las interfaces heredar las interfaces" ya que ninguna interfaz implementa realmente nada?
Jesse C. Slicer
2
El lenguaje habitual es que una interfaz "extiende" a su padre, y (al hacerlo) "hereda" los métodos de la interfaz principal, por lo que recomendaría usar "extender" aquí.
Theodore Murdock
Las interfaces pueden extender a los padres, entonces, ¿por qué sería una mala práctica? Si una interfaz es legítimamente un superconjunto de otra interfaz que, por supuesto, debería extenderla.
Robin Winslow

Respuestas:

10

Si bien las interfaces describen solo el comportamiento sin ninguna implementación, aún pueden participar en las jerarquías de herencia. Como ejemplo, imagine que tiene, por ejemplo, la idea común de a Collection. Esta interfaz proporcionaría algunas cosas que son comunes a todas las colecciones, como un método para iterar sobre los miembros. Luego puede pensar en algunas colecciones más específicas de objetos como a Listo a Set. Cada uno de estos hereda todos los requisitos de comportamiento de a Collection, pero agrega requisitos adicionales específicos para ese tipo particular de colección.

Siempre que tenga sentido crear una jerarquía de herencia para las interfaces, entonces debería hacerlo. Si siempre se encuentran dos interfaces juntas, entonces probablemente deberían fusionarse.

Zoe
fuente
6

Sí, pero solo si tiene sentido. Hágase las siguientes preguntas y si una o más se aplican, entonces es aceptable heredar una interfaz de otra.

  1. La interfaz A extiende lógicamente la interfaz B, por ejemplo, IList agrega funcionalidad a ICollection.
  2. ¿La interfaz A necesita definir el comportamiento ya especificado por otra interfaz, por ejemplo, implementando IDisposable si tiene recursos que necesitan ser ordenados o IEnumerable si se puede repetir?
  3. ¿Cada implementación de la interfaz A requiere el comportamiento especificado por la interfaz B?

En su ejemplo, no creo que responda sí a ninguno de esos. Puede que sea mejor agregarlo como otra propiedad:

public interface IContextProvider
{
   IDataContext DataContext { get; set; }
   IAreaContext AreaContext { get; set; }
   IEventContextFactory EventContextFactory { get; set; }
}
Trevor Pilley
fuente
¿Cómo sería mejor convertirlo en una propiedad que extender la interfaz, o es solo otra forma de desollar al gato, por así decirlo?
dreza
1
La diferencia es que si haces IContextProviderextender IEventContextFactory, entonces la ContextProviderimplementación necesitará implementar ese método también. Si lo agrega como una propiedad, está diciendo que a ContextProvidertiene un IEventContextFactorypero no conoce los detalles de implementación.
Trevor Pilley
4

Sí, está bien extender una interfaz de otra.

La pregunta de si es lo correcto en un caso específico depende de si existe una verdadera relación "es-a" entre los dos.

¿Qué se requiere para una relación válida "es-a"?

Primero, debe haber una diferencia real entre las dos interfaces, es decir, debe haber instancias que implementen la interfaz principal pero no la interfaz secundaria. Si no hay y nunca habrá instancias que no implementen ambas interfaces, entonces las dos interfaces deberían fusionarse.

En segundo lugar, debe darse el caso de que cada instancia de la interfaz secundaria debe ser necesariamente una instancia de la principal: no existe una lógica (razonable) bajo la cual crearía una instancia de la interfaz secundaria que no tendría sentido como una instancia de La interfaz principal.

Si ambos son ciertos, entonces la herencia es la relación correcta para las dos interfaces.

Theodore Murdock
fuente
Creo que no es necesario tener clases que usen solo la interfaz base y no la derivada. Por ejemplo, la interfaz IReadOnlyListpuede ser útil, incluso si se implementan todas mis clases de lista IList(que se deriva de IReadOnlyList).
svick
1

Si una interfaz siempre quiere requerir / proporcionar la otra, entonces adelante. He visto esto en algunos casos con IDisposibleeso tiene sentido. Sin embargo, en general, debe asegurarse de que al menos una de las interfaces se comporte más como un rasgo que como una responsabilidad.

Para su ejemplo, no está claro. Si ambos están siempre juntos, solo haga una interfaz. Si uno siempre necesita al otro, me preocuparía el tipo de herencia "X y 1" que muy a menudo conlleva múltiples responsabilidades y / u otros problemas de abstracción con fugas.

Telastyn
fuente
Gracias por eso. ¿Qué quiere decir con comportarse más como un rasgo que como una responsabilidad?
dreza
@dreza Me refiero a la responsabilidad como en SRP en SOLID, y rasgo como algo parecido a los rasgos de Scala. Principalmente el concepto de cómo la interfaz modela su problema. ¿Representa una parte primaria del problema o una vista en una pieza común de tipos de otro modo dispares?
Telastyn