Implementieren einer Schnittstelle, wenn Sie keine der Eigenschaften benötigen

31

Ziemlich einfach. Ich implementiere eine Schnittstelle, aber es gibt eine Eigenschaft, die für diese Klasse nicht erforderlich ist und eigentlich nicht verwendet werden sollte. Meine anfängliche Idee war, einfach etwas zu tun wie:

int IFoo.Bar
{
    get { raise new NotImplementedException(); }
}

Ich nehme an, dass daran nichts auszusetzen ist, aber es fühlt sich nicht "richtig" an. Hat jemand eine ähnliche Situation schon einmal erlebt? Wenn ja, wie sind Sie damit umgegangen?

Chris Pratt
quelle
1
Ich erinnere mich vage, dass es in C # eine häufig verwendete Klasse gibt, die eine Schnittstelle implementiert, in der Dokumentation jedoch ausdrücklich angibt, dass eine bestimmte Methode nicht implementiert ist. Ich werde versuchen zu sehen, ob ich es finden kann.
Magier Xy
Das würde mich auf jeden Fall interessieren, wenn Sie es finden können.
Chris Pratt
23
Ich kann in der .NET-Bibliothek auf mehrere Fälle hinweisen - UND SIE WERDEN ALLE ALS SCHLECHTE FALSCHHEITEN ANERKANNT . Dies ist eine ikonische und weit verbreitete Verletzung des Liskov-Substitutionsprinzips - Gründe , die LSP nicht verletzen, sind in meiner Antwort hier zu finden
Jimmy Hoffa,
3
Müssen Sie diese spezifische Schnittstelle implementieren, oder können Sie eine Super-Schnittstelle einführen und diese verwenden?
Christopher Schultz
6
"Eine Eigenschaft, die für diese Klasse nicht erforderlich ist" - Ob ein Teil einer Schnittstelle erforderlich ist, liegt bei den Clients der Schnittstelle, nicht bei den Implementierern. Wenn eine Klasse ein Mitglied einer Schnittstelle nicht sinnvoll implementieren kann, ist die Klasse nicht für die Schnittstelle geeignet. Dies kann bedeuten, dass die Benutzeroberfläche schlecht gestaltet ist - wahrscheinlich versucht sie zu viel zu tun -, aber das hilft der Klasse nicht.
Sebastian Redl

Antworten:

51

Dies ist ein klassisches Beispiel dafür, wie Menschen gegen das Liskov-Subtitutionsprinzip verstoßen. Ich rate nachdrücklich davon ab, würde aber möglicherweise eine andere Lösung vorschlagen:

  1. Möglicherweise bietet die Klasse, die Sie schreiben, nicht die von der Schnittstelle vorgeschriebene Funktionalität, wenn nicht alle Mitglieder der Schnittstelle verwendet werden.
  2. Alternativ kann diese Schnittstelle mehrere Aufgaben ausführen und nach dem Prinzip der Schnittstellentrennung getrennt werden.

Wenn das erste für Sie zutrifft, implementieren Sie die Schnittstelle einfach nicht in dieser Klasse. Denken Sie daran , wie eine Steckdose , wo der Boden Loch ist nicht erforderlich , so dass es nicht tatsächlich zu Boden befestigen. Sie stecken nichts mit Masse in und keine große Sache! Aber sobald Sie etwas benutzen, das einen Boden braucht, könnten Sie einen spektakulären Fehlschlag erleben. Besser nicht ein Fake-Ground-Loch hineinstecken. Wenn Ihre Klasse also nicht wirklich das tut, was die Schnittstelle vorsieht, implementieren Sie die Schnittstelle nicht.


Hier sind ein paar kurze Abschnitte aus Wikipedia:

Das Liskov-Substitutionsprinzip kann einfach wie folgt formuliert werden: "Stärken Sie keine Vorbedingungen und schwächen Sie keine Nachbedingungen".

Formal ist das Liskov-Substitutionsprinzip (LSP) eine spezielle Definition einer Subtypisierungsrelation, die als (starke) Verhaltenssubtypisierung bezeichnet wird und von Barbara Liskov 1987 in einer Grundsatzrede zur Datenabstraktion und -hierarchie eingeführt wurde. Es ist eine semantische und nicht nur syntaktische Beziehung , weil es die semantische Interoperabilität von Typen in einer Hierarchie zu gewährleisten beabsichtigt , [...]

Für die semantische Interoperabilität und Substituierbarkeit zwischen verschiedenen Implementierungen derselben Verträge müssen sich alle auf dasselbe Verhalten festlegen.


Das Prinzip der Schnittstellensegregation basiert auf der Idee, dass Schnittstellen in zusammenhängende Mengen unterteilt werden sollten, sodass Sie keine Schnittstelle benötigen, die viele unterschiedliche Aufgaben ausführt, wenn Sie nur eine Einrichtung benötigen . Denken Sie noch einmal an die Schnittstelle einer Steckdose, es könnte auch einen Thermostat haben, aber es würde die Installation einer Steckdose erschweren und die Verwendung für nicht beheizte Zwecke erschweren. Wie eine Steckdose mit Thermostat sind große Schnittstellen schwer zu implementieren und zu verwenden.

Das Prinzip der Schnittstellentrennung (ISP) besagt, dass kein Client gezwungen werden sollte, von Methoden abhängig zu sein, die er nicht verwendet. [1] ISP teilt sehr große Schnittstellen in kleinere und spezifischere auf, sodass die Kunden nur die Methoden kennen müssen, die für sie von Interesse sind.

Jimmy Hoffa
quelle
Absolut. Dies ist wahrscheinlich der Grund, warum es sich für mich überhaupt nicht richtig anfühlte. Manchmal braucht man nur eine sanfte Erinnerung, dass man etwas Dummes tut. Vielen Dank.
Chris Pratt
@ChrisPratt Es ist ein sehr häufiger Fehler. Deshalb können Formalismen wertvoll sein. Durch die Klassifizierung von Codegerüchen können Sie sie schneller identifizieren und zuvor verwendete Lösungen aufrufen.
Jimmy Hoffa
4

Sieht für mich gut aus, wenn dies Ihre Situation ist.

Es scheint mir jedoch, dass Ihre Schnittstelle (oder deren Verwendung) kaputt ist, wenn eine ableitende Klasse nicht wirklich alles implementiert. Teilen Sie diese Schnittstelle auf.

Haftungsausschluss: Dies erfordert eine mehrfache Vererbung, und ich habe keine Ahnung, ob C # dies unterstützt.

Leichtigkeit Rennen mit Monica
quelle
6
C # unterstützt die Mehrfachvererbung von Klassen nicht, jedoch für Schnittstellen.
mgw854
2
Ich denke, du hast recht. Die Schnittstelle ist schließlich ein Vertrag, und selbst wenn ich weiß, dass diese bestimmte Klasse nicht so verwendet wird, dass sie alles kaputt macht, was die Schnittstelle nutzt, wenn diese Eigenschaft deaktiviert ist, ist das nicht offensichtlich.
Chris Pratt
@ ChrisPratt: Ja.
Leichtigkeit Rennen mit Monica
4

Ich bin auf diese Situation gestoßen. In der Tat, wie an anderer Stelle erwähnt, hat die BCL solche Fälle ... Ich werde versuchen, bessere Beispiele und einige Gründe zu liefern:

Wenn Sie eine bereits ausgelieferte Schnittstelle haben, die Sie aus Kompatibilitätsgründen aufbewahren und ...

  • Die Schnittstelle enthält Mitglieder, die veraltet oder unleserlich sind. Zum Beispiel BlockingCollection<T>.ICollection.SyncRoot(unter anderem), während ICollection.SyncRootper se nicht veraltet ist, wird es werfen NotSupportedException.

  • Die Schnittstelle enthält Elemente, von denen dokumentiert ist, dass sie optional sind und dass die Implementierung möglicherweise die Ausnahme auslöst. Zum Beispiel auf MSDN IEnumerator.Resetdazu heißt es:

Die Rücksetzmethode wird für die COM-Interoperabilität bereitgestellt. Es muss nicht unbedingt implementiert werden. Stattdessen kann der Implementierer einfach eine NotSupportedException auslösen.

  • Aus Versehen sollte es sich bei der Gestaltung der Benutzeroberfläche in erster Linie um mehr als eine Benutzeroberfläche handeln. In BCL ist es ein gängiges Muster, schreibgeschützte Versionen von Containern mit zu implementieren NotSupportedException. Ich habe es selbst gemacht, es ist das, was jetzt erwartet wird ... Ich kehre ICollection<T>.IsReadOnlyzurück, truedamit du es ihnen erzählen kannst. Das richtige Design wäre gewesen, eine lesbare Version der Schnittstelle zu haben, und dann wird die vollständige Schnittstelle von dieser übernommen.

  • Es gibt keine bessere Benutzeroberfläche. Ich habe zum Beispiel eine Klasse, mit der Sie über den Index auf Elemente zugreifen können. Überprüfen Sie, ob ein Element enthalten ist und in welchem ​​Index es eine bestimmte Größe hat. Sie können es in ein Array kopieren. Es scheint ein Job zu sein, IList<T>aber meine Klasse hat Eine feste Größe, und das Hinzufügen oder Entfernen wird nicht unterstützt, daher funktioniert es eher wie ein Array als wie eine Liste. Aber es gibt keine IArray<T>in der BCL .

  • Die Schnittstelle gehört zu einer API, die auf mehrere Plattformen portiert ist, und bei der Implementierung einer bestimmten Plattform werden einige Teile davon nicht unterstützt. Im Idealfall gibt es eine Möglichkeit, dies zu erkennen, sodass portabler Code, der eine solche API verwendet, entscheiden kann, ob diese Teile aufgerufen werden oder nicht NotSupportedException. Dies gilt insbesondere dann, wenn es sich um eine Portierung auf eine neue Plattform handelt, die im ursprünglichen Design nicht vorgesehen war.


Überlegen Sie auch, warum es nicht unterstützt wird?

Manchmal InvalidOperationExceptionist eine bessere Option. Eine weitere Möglichkeit zum Hinzufügen von Polymorphismus in einer Klasse besteht beispielsweise darin, eine interne Schnittstelle auf verschiedene Weise zu implementieren, und Ihr Code wählt die zu instanziierende Schnittstelle in Abhängigkeit von den im Konstruktor der Klasse angegebenen Parametern aus. [Dies ist besonders nützlich, wenn Sie wissen, dass der Optionssatz feststeht und Sie nicht zulassen möchten, dass Klassen von Drittanbietern durch Abhängigkeitsinjektion eingeführt werden.] Ich habe dies getan, um ThreadLocal zurück zu portieren, da es sich um eine Tracking- und eine Nicht-Tracking-Implementierung handelt zu weit appart, und was wirkt sich ThreadLocal.Valuesauf die Non-Tracking-Implementierung aus?InvalidOperationExceptionauch wenn es nicht vom Zustand des Objekts abhängt. In diesem Fall stellte ich die Klasse selbst vor und wusste, dass diese Methode implementiert werden musste, indem nur eine Ausnahme ausgelöst wurde.

Manchmal ist ein Standardwert sinnvoll. Zum Beispiel ist es aus den ICollection<T>.IsReadOnlyoben genannten Gründen sinnvoll, nur "wahr" oder "falsch" zurückzugeben, je nach Fall. Also ... was ist die Semantik von IFoo.Bar? Möglicherweise gibt es einen sinnvollen Standardwert, der zurückgegeben werden muss.


Nachtrag: Wenn Sie die Kontrolle über die Benutzeroberfläche haben (und aus Kompatibilitätsgründen nicht bei dieser bleiben müssen), sollte es keinen Fall geben, in den Sie werfen müssen NotSupportedException. Möglicherweise müssen Sie die Schnittstelle in zwei oder mehr kleinere Schnittstellen aufteilen, um die richtige Passform für Ihren Fall zu finden, was in Extremsituationen zu "Verschmutzung" führen kann.

Theraot
quelle
0

Hat jemand eine ähnliche Situation schon einmal erlebt?

Ja, die .Net-Bibliotheksdesigner haben es getan. Die Dictionary-Klasse macht das, was Sie tun. Es verwendet eine explizite Implementierung, um einige der IDictionary-Methoden effektiv auszublenden . Hier wird es besser erklärt , aber um zusammenzufassen, um die Dictionary-Methoden Add, CopyTo oder Remove zu verwenden, die KeyValuePairs verwenden, müssen Sie das Objekt zuerst in ein IDictionary umwandeln.

* Es ist nicht "versteckt" die Methoden im engeren Sinne des Wortes "verstecken", wie Microsoft es verwendet . Aber ich kenne in diesem Fall keinen besseren Begriff.

user2023861
quelle
... was schrecklich ist.
Leichtigkeit Rennen mit Monica
Warum die Gegenstimme?
user2023861
2
Wahrscheinlich, weil Sie die Frage nicht beantwortet haben. Ish. Art von.
Leichtigkeit Rennen mit Monica
@LightnessRacesinOrbit, ich habe meine Antwort aktualisiert, um es klarer zu machen. Ich hoffe es hilft dem OP.
user2023861
2
Scheint, als würde es mir die Frage beantworten. Die Tatsache, dass es sich um eine gute oder schlechte Idee handeln könnte, scheint nicht im Rahmen der Frage zu liegen, kann aber dennoch hilfreich sein, um Antworten anzuzeigen.
Ellesedil,
-6

Sie können immer nur einen harmlosen Wert oder einen Standardwert implementieren und zurückgeben. Immerhin ist es nur eine Eigenschaft. Es kann 0 (die Standardeigenschaft von int) oder einen beliebigen in Ihrer Implementierung sinnvollen Wert (int.MinValue, int.MaxValue, 1, 42 usw.) zurückgeben.

//We don't officially implement this property
int IFoo.Bar
{
     { get; }
}

Eine Ausnahme auszulösen scheint eine schlechte Form zu sein.

Jon Raynor
quelle
2
Warum? Wie können falsche Daten besser zurückgegeben werden?
Leichtigkeit Rennen mit Monica
1
Dies bricht LSP: Siehe Jimmy Hoffas Antwort für eine Erklärung, warum.
5
Wenn es keine möglichen korrekten Rückgabewerte gibt, ist es besser, eine Ausnahme auszulösen, als einen falschen Wert zurückzugeben. Unabhängig davon, was Sie tun, führt Ihr Programm zu einer Fehlfunktion, wenn es versehentlich diese Eigenschaft aufruft. Aber wenn Sie eine Ausnahme auslösen, wird es offensichtlich sein, warum es nicht funktioniert.
Tanner Swett
Ich weiß jetzt nicht, wie der Wert falsch wäre, wenn Sie die Schnittstelle implementieren! Es ist Ihre Implementierung, es kann alles sein, was Sie wollen.
Jon Raynor
Was ist, wenn der richtige Wert zum Zeitpunkt der Kompilierung nicht bekannt war?
Andy