Les interfaces devraient-elles étendre (et hériter ainsi des méthodes) d'autres interfaces

13

Bien qu'il s'agisse d'une question générale, elle est également spécifique à un problème que je rencontre actuellement. J'ai actuellement une interface spécifiée dans ma solution appelée

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

Cette interface est souvent utilisée tout au long du programme et j'ai donc un accès facile aux objets dont j'ai besoin. Cependant, à un niveau assez bas d'une partie de mon programme, j'ai besoin d'accéder à une autre classe qui utilisera IAreaContext et effectuera certaines opérations hors de celle-ci. J'ai donc créé une autre interface d'usine pour faire cette création appelée:

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

J'ai une classe qui implémente IContextProvider et est injectée à l'aide de NinJect. Le problème que j'ai est que la zone où j'ai besoin d'utiliser ce IEventContextFactory a accès au IContextProvider uniquement et utilise elle-même une autre classe qui aura besoin de cette nouvelle interface. Je ne veux pas avoir à instancier cette implémentation d' IEventContextFactory au bas niveau et préfère plutôt travailler avec l' interface IEventContextFactory . Cependant, je ne veux pas non plus avoir à injecter un autre paramètre via les constructeurs juste pour le faire passer à la classe qui en a besoin, c'est-à-dire

// 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();      
    }
}

Donc ma question principale est, est-ce que cela serait acceptable ou est-ce même une pratique courante ou bonne de faire quelque chose comme ça (interface étendre une autre interface):

public interface IContextProvider : IEventContextFactory

ou devrais-je envisager de meilleures alternatives pour atteindre ce dont j'ai besoin. Si je n'ai pas fourni suffisamment d'informations pour faire des suggestions, faites-le moi savoir et je peux en fournir davantage.

dreza
la source
2
Je reformulerais le titre de votre question comme "Les interfaces devraient-elles hériter des interfaces" car aucune interface n'implémente réellement quoi que ce soit.
Jesse C. Slicer
2
Le langage habituel est qu'une interface "étend" son parent et (ce faisant) "hérite" des méthodes de l'interface parent, donc je recommanderais d'utiliser "étendre" ici.
Theodore Murdock
Les interfaces peuvent étendre les parents, alors pourquoi serait-ce une mauvaise pratique? Si une interface est légitimement un super-ensemble d'une autre interface que bien sûr, elle devrait l'étendre.
Robin Winslow

Réponses:

10

Bien que les interfaces décrivent uniquement le comportement sans implémentation, elles peuvent toujours participer aux hiérarchies d'héritage. Par exemple, imaginez que vous ayez, disons, l'idée commune d'un Collection. Cette interface fournirait quelques éléments communs à toutes les collections, comme une méthode pour parcourir les membres. Ensuite, vous pouvez penser à des collections d'objets plus spécifiques tels que a Listou a Set. Chacun d'eux hérite de toutes les exigences de comportement de a Collection, mais ajoute des exigences supplémentaires spécifiques à ce type particulier de collection.

Chaque fois qu'il est logique de créer une hiérarchie d'héritage pour les interfaces, vous devriez. Si deux interfaces sont toujours trouvées ensemble, elles devraient probablement être fusionnées.

Zoé
la source
6

Oui, mais seulement si cela a du sens. Posez-vous les questions suivantes et si une ou plusieurs s'appliquent, il est acceptable d'hériter d'une interface d'une autre.

  1. L'interface A étend-elle logiquement l'interface B, par exemple IList en ajoutant des fonctionnalités à ICollection.
  2. Interface Est-il nécessaire de définir un comportement déjà spécifié par une autre interface, par exemple en mettant en œuvre IDisposable si elle a des ressources qui doivent être réorganisées ou IEnumerable si elle peut être itérée.
  3. Chaque implémentation de l'interface A nécessite-t-elle le comportement spécifié par l'interface B?

Dans votre exemple, je ne pense pas que cela répond oui à aucun de ceux-là. Vous feriez peut-être mieux de l'ajouter comme autre propriété:

public interface IContextProvider
{
   IDataContext DataContext { get; set; }
   IAreaContext AreaContext { get; set; }
   IEventContextFactory EventContextFactory { get; set; }
}
Trevor Pilley
la source
Comment serait-il préférable d'en faire une propriété que d'étendre l'interface, ou est-ce juste une autre façon de dépouiller le chat pour ainsi dire?
dreza
1
La différence est que si vous faites IContextProviderétendre IEventContextFactory, l' ContextProviderimplémentation devra également implémenter cette méthode. Si vous l'ajoutez en tant que propriété, vous dites qu'un a ContextProvidera IEventContextFactorymais ne connaît pas réellement les détails de l'implémentation.
Trevor Pilley
4

Oui, c'est bien d'étendre une interface d'une autre.

La question de savoir si c'est la bonne chose à faire dans un cas spécifique dépend de l'existence ou non d'une véritable relation «est-un» entre les deux.

Qu'est-ce qui est requis pour une relation «est-un» valide?

Premièrement, il doit y avoir une réelle différence entre les deux interfaces, c'est-à-dire qu'il doit y avoir des instances qui implémentent l'interface parent mais pas l'interface enfant. S'il n'y a pas et qu'il n'y aura jamais d'instances qui n'implémentent pas les deux interfaces, les deux interfaces doivent à la place être fusionnées.

Deuxièmement, il doit être le cas que chaque instance de l'interface enfant doit nécessairement être une instance du parent: il n'y a pas de logique (raisonnable) selon laquelle vous créeriez une instance de l'interface enfant qui n'aurait pas de sens en tant qu'instance de l'interface parent.

Si ces deux éléments sont vrais, l'héritage est la bonne relation pour les deux interfaces.

Theodore Murdock
la source
Je pense qu'il n'est pas nécessaire d'avoir des classes qui utilisent uniquement l'interface de base et non celle dérivée. Par exemple, l'interface IReadOnlyListpeut être utile, même si toutes mes classes de liste implémentent IList(qui est dérivée de IReadOnlyList).
svick
1

Si une interface veut toujours exiger / fournir l'autre, alors allez-y. J'ai vu cela dans quelques cas IDisposiblequi avaient du sens. En général cependant, vous devez vous assurer qu'au moins une des interfaces se comporte plus comme un trait que comme une responsabilité.

Pour votre exemple, ce n'est pas clair. S'ils sont tous les deux toujours ensemble, créez une seule interface. Si l'un a toujours besoin de l'autre, je serais préoccupé par le type d'héritage "X et 1" qui conduit très souvent à de multiples responsabilités et / ou à d'autres problèmes d'abstraction qui fuient.

Telastyn
la source
Merci pour ça. Qu'entendez-vous par se comporter plus comme un trait que comme une responsabilité?
dreza
@dreza Je veux dire responsabilité comme dans SRP dans SOLID, et trait comme quelque chose comme les traits Scala. Surtout le concept de la façon dont l'interface modélise votre problème. Représente-t-il une partie principale du problème ou une vue dans un morceau commun de types autrement disparates?
Telastyn