Le interfacce dovrebbero estendere (e in tal modo ereditare i metodi di) altre interfacce


13

Sebbene questa sia una domanda generale, è anche specifica per un problema che sto vivendo. Attualmente ho un'interfaccia specificata nella mia soluzione chiamata

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

Questa interfaccia è spesso utilizzata in tutto il programma e quindi ho un facile accesso agli oggetti di cui ho bisogno. Tuttavia, a un livello abbastanza basso di una parte del mio programma, ho bisogno di accedere a un'altra classe che utilizzerà IAreaContext ed eseguirà alcune operazioni al di fuori di esso. Quindi ho creato un'altra interfaccia di fabbrica per fare questa creazione chiamata:

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

Ho una classe che implementa IContextProvider e viene iniettata usando NinJect. Il problema che ho è che l'area in cui devo utilizzare questo IEventContextFactory ha accesso solo a IContextProvider e utilizza essa stessa un'altra classe che avrà bisogno di questa nuova interfaccia. Non voglio istanziare questa implementazione di IEventContextFactory a basso livello e preferirei lavorare con l' interfaccia IEventContextFactory in tutto. Tuttavia, anche io non voglio dover iniettare un altro parametro attraverso i costruttori solo per farlo passare alla classe che ne ha bisogno cioè

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

Quindi la mia domanda principale è: sarebbe accettabile o è addirittura comune o buona pratica fare qualcosa del genere (l'interfaccia estende un'altra interfaccia):

public interface IContextProvider : IEventContextFactory

o dovrei considerare alternative migliori per raggiungere ciò di cui ho bisogno. Se non ho fornito abbastanza informazioni per dare suggerimenti, fammelo sapere e posso fornire di più.


2
Riformulerei il titolo della tua domanda come "Le interfacce dovrebbero ereditare le interfacce" poiché nessuna interfaccia implementa effettivamente nulla.
Jesse C. Slicer,

2
La solita lingua è che un'interfaccia "estende" il suo genitore e (nel farlo) "eredita" i metodi dell'interfaccia genitore, quindi consiglierei di usare "estende" qui.
Theodore Murdock,

Le interfacce possono estendere i genitori, quindi perché sarebbe una cattiva pratica? Se un'interfaccia è legittimamente un super-set di un'altra interfaccia rispetto a quella ovviamente dovrebbe estenderla.
Robin Winslow,

Risposte:


10

Mentre le interfacce descrivono solo comportamenti senza alcuna implementazione, possono comunque partecipare alle gerarchie di ereditarietà. Ad esempio, immagina di avere, diciamo, l'idea comune di a Collection. Questa interfaccia fornirebbe alcune cose comuni a tutte le raccolte, come un metodo per scorrere i membri. Quindi puoi pensare ad alcune raccolte più specifiche di oggetti come a Listo a Set. Ognuno di questi eredita tutti i requisiti comportamentali di a Collection, ma aggiunge ulteriori requisiti specifici per quel particolare tipo di raccolta.

Ogni volta che ha senso creare una gerarchia ereditaria per le interfacce, allora dovresti. Se due interfacce verranno sempre trovate insieme, probabilmente dovrebbero essere unite.


6

Sì, ma solo se ha senso. Ponetevi le seguenti domande e se una o più si applicano, allora è accettabile ereditare un'interfaccia da un'altra.

  1. L'interfaccia A estende logicamente l'interfaccia B, ad esempio IList aggiungendo funzionalità a ICollection.
  2. Interfaccia È necessario definire un comportamento già specificato da un'altra interfaccia, ad esempio implementando IDisposable se dispone di risorse che devono essere riordinate o IEnumerable se può essere ripetuto.
  3. Ogni implementazione dell'interfaccia A richiede il comportamento specificato dall'interfaccia B?

Nel tuo esempio, non credo che risponda sì a nessuno di questi. Potrebbe essere meglio aggiungerlo come un'altra proprietà:

public interface IContextProvider
{
   IDataContext DataContext { get; set; }
   IAreaContext AreaContext { get; set; }
   IEventContextFactory EventContextFactory { get; set; }
}

Come renderlo una proprietà sarebbe meglio che estendere l'interfaccia, o è solo un altro modo di scuoiare il gatto per così dire?
Dreza,

1
La differenza è che se IContextProviderestendi IEventContextFactory, l' ContextProviderimplementazione dovrà implementare anche quel metodo. Se lo aggiungi come proprietà, stai dicendo che a ContextProviderha un IEventContextFactoryma in realtà non conosce i dettagli di implementazione.
Trevor Pilley,

4

Sì, va bene estendere un'interfaccia da un'altra.

La questione se sia la cosa giusta da fare in un caso specifico dipende dal fatto che esista o meno una relazione "is-a" tra i due.

Cosa è necessario per una relazione "is-a" valida?

Innanzitutto, ci deve essere una reale differenza tra le due interfacce, ovvero devono esserci istanze che implementano l'interfaccia padre ma non l'interfaccia figlio. Se non ci sono e non ci saranno mai istanze che non implementano entrambe le interfacce, le due interfacce dovrebbero invece essere unite.

In secondo luogo, deve accadere che ogni istanza dell'interfaccia figlio debba necessariamente essere un'istanza del genitore: non esiste una logica (ragionevole) in base alla quale si creerebbe un'istanza dell'interfaccia figlio che non avrebbe senso come istanza di l'interfaccia genitore.

Se entrambi sono veri, l'ereditarietà è la relazione giusta per le due interfacce.


Penso che non siano necessarie classi che usano solo l'interfaccia di base e non quella derivata. Ad esempio, l'interfaccia IReadOnlyListpuò essere utile, anche se tutte le mie classi di elenco implementano IList(da cui deriva IReadOnlyList).
svick,

1

Se una delle interfacce vuole sempre richiedere / fornire l'altra, vai avanti. L'ho visto in alcuni casi IDisposibleche ha avuto un senso. In generale, però, dovresti assicurarti che almeno una delle interfacce si comporti più come un tratto che come una responsabilità.

Per il tuo esempio, non è chiaro. Se sono sempre entrambi insieme, crea un'interfaccia. Se uno ha sempre bisogno dell'altro, sarei preoccupato per il tipo di eredità "X e 1" che molto spesso porta a molteplici responsabilità e / o altri problemi di astrazione che perde.


Grazie per quello Cosa intendi per comportarti più come un tratto che come una responsabilità?
Dreza,

@dreza Intendo la responsabilità come in SRP in SOLID, e tratto come qualcosa come i tratti di Scala. Principalmente il concetto di come l'interfaccia modella il tuo problema. Rappresenta una parte principale del problema o una vista in un pezzo comune di tipi altrimenti disparati?
Telastyn,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.