Sollten Schnittstellen andere Schnittstellen erweitern (und dabei Methoden erben)?

13

Obwohl dies eine allgemeine Frage ist, ist sie auch spezifisch für ein Problem, auf das ich momentan stoße. Ich habe derzeit eine in meiner Lösung angegebene Schnittstelle namens

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

Diese Schnittstelle wird oft im gesamten Programm verwendet und daher habe ich einfachen Zugriff auf die Objekte, die ich benötige. Auf einer relativ niedrigen Ebene eines Teils meines Programms benötige ich jedoch Zugriff auf eine andere Klasse, die IAreaContext verwendet und einige Operationen außerhalb dieser Klasse ausführt . Deshalb habe ich eine andere Factory-Oberfläche erstellt, die folgende Bezeichnung hat:

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

Ich habe eine Klasse, die den IContextProvider implementiert und mit NinJect injiziert wird. Das Problem, das ich habe, ist, dass der Bereich, in dem ich diesen IEventContextFactory verwenden muss, nur Zugriff auf den IContextProvider hat und selbst eine andere Klasse verwendet, die diese neue Schnittstelle benötigt. Ich möchte diese Implementierung von IEventContextFactory nicht auf der niedrigen Ebene instanziieren müssen und möchte lieber durchgehend mit der IEventContextFactory- Schnittstelle arbeiten. Ich möchte jedoch auch keinen weiteren Parameter über die Konstruktoren einfügen müssen, nur um ihn an die Klasse weiterzuleiten, die ihn benötigt, d. H

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

Meine Hauptfrage ist also, ob dies akzeptabel ist oder ob es überhaupt üblich ist, so etwas zu tun (Interface erweitere ein anderes Interface):

public interface IContextProvider : IEventContextFactory

oder sollte ich bessere Alternativen in Betracht ziehen, um das zu erreichen, was ich brauche? Wenn ich nicht genügend Informationen zur Verfügung gestellt habe, um Vorschläge zu machen, lass es mich wissen und ich kann mehr liefern.

dreza
quelle
2
Ich würde Ihren Fragentitel als "Sollten Schnittstellen Schnittstellen erben" umformulieren, da keine Schnittstelle tatsächlich etwas implementiert.
Jesse C. Slicer
2
Die übliche Sprache ist, dass eine Schnittstelle ihre übergeordnete Schnittstelle "erweitert" und dabei die Methoden der übergeordneten Schnittstelle "erbt". Daher würde ich hier die Verwendung von "Erweitern" empfehlen.
Theodore Murdock
Schnittstellen können die Eltern erweitern. Warum sollte es also eine schlechte Praxis sein? Wenn eine Schnittstelle zu Recht eine Übermenge einer anderen Schnittstelle ist, sollte sie diese natürlich erweitern.
Robin Winslow

Antworten:

10

Während Interfaces nur das Verhalten ohne Implementierung beschreiben, können sie dennoch an Vererbungshierarchien teilnehmen. Stellen Sie sich beispielsweise vor, Sie hätten die gemeinsame Idee von a Collection. Diese Schnittstelle bietet einige Gemeinsamkeiten für alle Auflistungen, z. B. eine Methode zum Durchlaufen der Member. Dann können Sie sich einige spezifischere Sammlungen von Objekten wie a Listoder a überlegen Set. Jeder von diesen erbt alle Verhaltensanforderungen von a Collection, fügt jedoch zusätzliche Anforderungen hinzu, die für diesen bestimmten Sammlungstyp spezifisch sind.

Wann immer es sinnvoll ist, eine Vererbungshierarchie für Schnittstellen zu erstellen, sollten Sie dies tun. Wenn zwei Schnittstellen immer zusammen gefunden werden, sollten sie wahrscheinlich zusammengeführt werden.

Zoe
quelle
6

Ja, aber nur wenn es Sinn macht. Stellen Sie sich die folgenden Fragen, und wenn eine oder mehrere zutreffen, ist es akzeptabel, eine Schnittstelle von einer anderen zu erben.

  1. Erweitert die Schnittstelle A die Schnittstelle B logisch, z. B. indem IList die Funktionalität zu ICollection hinzufügt.
  2. Muss Schnittstelle A das Verhalten definieren, das bereits von einer anderen Schnittstelle angegeben wurde, z. B. IDisposable implementieren, wenn Ressourcen vorhanden sind, die aufgeräumt werden müssen, oder IEnumerable, wenn es wiederholt werden kann.
  3. Erfordert jede Implementierung von Schnittstelle A das von Schnittstelle B festgelegte Verhalten?

In Ihrem Beispiel antwortet es meiner Meinung nach auf keines von diesen mit Ja. Sie können es besser als eine andere Eigenschaft hinzufügen:

public interface IContextProvider
{
   IDataContext DataContext { get; set; }
   IAreaContext AreaContext { get; set; }
   IEventContextFactory EventContextFactory { get; set; }
}
Trevor Pilley
quelle
Wie wäre es besser, eine Eigenschaft daraus zu machen, als die Benutzeroberfläche zu erweitern, oder ist es einfach eine andere Art, die Katze sozusagen zu häuten?
Dreza
1
Der Unterschied besteht darin, dass die Implementierung auch diese Methode implementieren muss , wenn Sie die IContextProviderErweiterung vornehmen . Wenn Sie es als eine Eigenschaft hinzufügen, sagen Sie, dass a ein hat, aber die Implementierungsdetails nicht wirklich kennt. IEventContextFactoryContextProviderContextProviderIEventContextFactory
Trevor Pilley
4

Ja, es ist in Ordnung, eine Schnittstelle von einer anderen zu erweitern.

Die Frage, ob es in einem bestimmten Fall das Richtige ist, hängt davon ab, ob es eine wahre "Ist-Ein" -Beziehung zwischen den beiden gibt.

Was ist für eine gültige "is-a" -Beziehung erforderlich?

Erstens muss es einen echten Unterschied zwischen den beiden Schnittstellen geben, dh es muss Instanzen geben, die die übergeordnete Schnittstelle implementieren, nicht jedoch die untergeordnete Schnittstelle. Wenn es keine Instanzen gibt, die nicht beide Schnittstellen implementieren, sollten die beiden Schnittstellen stattdessen zusammengeführt werden.

Zweitens muss es so sein, dass jede Instanz der untergeordneten Schnittstelle unbedingt eine Instanz der übergeordneten Schnittstelle sein muss: Es gibt keine (sinnvolle) Logik, unter der Sie eine Instanz der untergeordneten Schnittstelle erstellen würden, deren Instanz keinen Sinn ergeben würde die übergeordnete Schnittstelle.

Wenn beides zutrifft, ist die Vererbung die richtige Beziehung für die beiden Schnittstellen.

Theodore Murdock
quelle
Ich denke, Klassen, die nur die Basisschnittstelle und nicht die abgeleitete verwenden, sind nicht erforderlich. Beispielsweise kann die Schnittstelle IReadOnlyListnützlich sein, auch wenn alle meine Listenklassen implementieren IList(von denen abgeleitet ist IReadOnlyList).
Svick
1

Ist die eine Schnittstelle immer die andere benötigen / bereitstellen möchte, fahren Sie fort. Ich habe dies in einigen Fällen gesehen IDisposible, die Sinn ergeben haben. Im Allgemeinen sollten Sie jedoch sicherstellen, dass sich mindestens eine der Schnittstellen eher wie eine Eigenschaft als eine Verantwortung verhält.

Für Ihr Beispiel ist es nicht klar. Wenn beide immer zusammen sind, erstellen Sie einfach eine Schnittstelle. Wenn einer immer den anderen braucht, würde ich mir Sorgen machen über die Art der Vererbung "X und 1", die sehr oft zu mehreren Verantwortlichkeiten und / oder anderen undichten Abstraktionsproblemen führt.

Telastyn
quelle
Dank dafür. Was meinst du damit, dass du dich eher wie ein Charakterzug als wie eine Verantwortung benimmst?
Dreza
@dreza Ich meine Verantwortung wie bei SRP in SOLID und Eigenschaft wie bei Scala. Hauptsächlich das Konzept, wie die Benutzeroberfläche Ihr Problem modelliert. Stellt es einen Hauptteil des Problems dar oder einen Blick auf ein gemeinsames Stück ansonsten unterschiedlicher Typen?
Telastyn