Gibt es ein Interface-Prinzip "Fragen Sie nur nach dem, was Sie brauchen"?

9

Ich habe mich dazu entwickelt, ein Prinzip für das Entwerfen und Konsumieren von Schnittstellen zu verwenden, das im Grunde sagt: "Fragen Sie nur nach dem, was Sie brauchen."

Wenn ich zum Beispiel eine Reihe von Typen habe, die gelöscht werden können, erstelle ich eine DeletableSchnittstelle:

interface Deletable {
   void delete();
}

Dann kann ich eine generische Klasse schreiben:

class Deleter<T extends Deletable> {
   void delete(T t) {
      t.delete();
   }
}

An anderer Stelle im Code werde ich immer um die kleinstmögliche Verantwortung bitten, um die Anforderungen des Client-Codes zu erfüllen. Wenn ich also nur a löschen muss File, frage ich immer noch nach a Deletable, nicht nach a File.

Ist dieses Prinzip allgemein bekannt und hat bereits einen akzeptierten Namen? Ist es umstritten? Wird es in Lehrbüchern diskutiert?

glenviewjeff
quelle
1
Lose Kupplung vielleicht? Oder enge Schnittstellen?
tdammers

Antworten:

16

Ich glaube, dass sich dies auf das bezieht, was Robert Martin das Prinzip der Schnittstellentrennung nennt . Die Schnittstellen sind in kleine und übersichtliche Schnittstellen unterteilt, sodass die Verbraucher (Kunden) nur die Methoden kennen müssen, die für sie von Interesse sind. Sie können mehr über SOLID erfahren .

Vadim
quelle
4

Um die sehr gute Antwort von Vadim zu erweitern, werde ich die Frage "Ist es umstritten?" Mit "Nein, nicht wirklich" beantworten.

Im Allgemeinen ist die Schnittstellentrennung eine gute Sache, da die Gesamtzahl der "Änderungsgründe" der verschiedenen beteiligten Objekte verringert wird. Das Kernprinzip ist, wenn eine Schnittstelle mit mehreren Methoden geändert werden muss, beispielsweise um einer der Schnittstellenmethoden einen Parameter hinzuzufügen, müssen alle Konsumenten der Schnittstelle zumindest neu kompiliert werden, auch wenn sie die geänderte Methode nicht verwendet haben. "Aber es ist nur eine Neukompilierung!", Höre ich Sie sagen; Das mag wahr sein, aber denken Sie daran, dass normalerweise alles, was Sie neu kompilieren, als Teil eines Software-Patches herausgeschoben werden muss, egal wie bedeutend die Änderung an der Binärdatei ist. Diese Regeln wurden ursprünglich in den frühen 90er Jahren entwickelt, als die durchschnittliche Desktop-Workstation weniger leistungsfähig war als das Telefon in Ihrer Tasche, die Einwahl von 14,4 KB Baud war und 3,5 "1,44 MB" -Disketten die primären Wechselmedien waren. Selbst in der aktuellen Ära von 3G / 4G haben Benutzer von drahtlosem Internet häufig Datenpläne mit Einschränkungen. Wenn also ein Upgrade veröffentlicht wird, ist es umso besser, je weniger Binärdateien heruntergeladen werden müssen.

Wie bei allen guten Ideen kann die Schnittstellentrennung jedoch bei unsachgemäßer Implementierung schlecht werden. Zunächst einmal besteht die Möglichkeit, dass Sie durch Trennen von Schnittstellen, während das Objekt, das diese Schnittstellen implementiert (wobei die Abhängigkeiten erfüllt werden), relativ unverändert bleibt, eine "Hydra" erhalten, eine Verwandte des "God Object" -Antimusters, in dem die Die allwissende, allmächtige Natur des Objekts wird durch die engen Schnittstellen vor den Abhängigen verborgen. Am Ende haben Sie ein vielköpfiges Monster, das mindestens so schwer zu warten ist wie das Gott-Objekt, und den Aufwand für die Wartung aller seiner Schnittstellen. Es gibt keine feste Anzahl von Schnittstellen, die Sie nicht überschreiten sollten, aber jeder Schnittstelle, die Sie für ein einzelnes Objekt implementieren, sollte die Frage "Trägt diese Schnittstelle zum Objekt bei" vorangestellt werden.

Zweitens ist eine Schnittstelle pro Methode möglicherweise nicht erforderlich, ungeachtet dessen, was SRP Ihnen möglicherweise sagt. Sie können mit "Ravioli-Code" enden; So viele mundgerechte Stücke, dass es schwierig ist, genau herauszufinden, wo die Dinge tatsächlich passieren. Es ist auch nicht erforderlich, eine Schnittstelle mit zwei Methoden aufzuteilen, wenn alle aktuellen Benutzer dieser Schnittstelle beide Methoden benötigen. Selbst wenn eine der abhängigen Klassen nur eine der beiden Methoden benötigt, ist es im Allgemeinen akzeptabel, die Schnittstelle nicht zu teilen, wenn ihre Methoden konzeptionell eine sehr hohe Kohäsion aufweisen (gute Beispiele sind "antonymische Methoden", die exakte Gegensätze zueinander sind).

Die Schnittstellentrennung sollte auf den Klassen basieren, die von der Schnittstelle abhängig sind:

  • Wenn nur eine Klasse von der Schnittstelle abhängig ist, trennen Sie nicht. Wenn die Klasse keine oder mehrere der Schnittstellenmethoden verwendet und dies der einzige Verbraucher der Schnittstelle ist, sollten Sie diese Methoden wahrscheinlich gar nicht erst verfügbar machen.

  • Wenn es mehr als eine Klasse gibt, die von der Schnittstelle abhängig ist und alle abhängigen Personen alle Methoden der Schnittstelle verwenden, trennen Sie sie nicht. Wenn Sie die Schnittstelle ändern müssen (um eine Methode hinzuzufügen oder eine Signatur zu ändern), sind alle aktuellen Verbraucher von der Änderung betroffen, unabhängig davon, ob Sie sie trennen oder nicht (wenn Sie jedoch eine Methode hinzufügen, die mindestens eine abhängige Person nicht benötigt, sollten Sie dies berücksichtigen vorsichtig, wenn die Änderung stattdessen als neue Schnittstelle implementiert werden soll (möglicherweise von der vorhandenen ererbend).

  • Wenn mehr als eine Klasse von der Schnittstelle abhängig ist und nicht dieselben Methoden verwendet werden, ist dies ein Kandidat für die Trennung. Schauen Sie sich die "Kohärenz" der Schnittstelle an. fördern alle Methoden ein einziges, sehr spezifisches Programmierziel? Wenn Sie mehr als einen Hauptzweck für die Schnittstelle (und ihre Implementierer) identifizieren können, sollten Sie die Schnittstellen entlang dieser Linien aufteilen, um kleinere Schnittstellen mit weniger "Änderungsgründen" zu erstellen.

KeithS
quelle
Es ist auch erwähnenswert, dass die Schnittstellentrennung in Ordnung und schwierig sein kann, wenn eine OOP-Sprache / ein OOP-System verwendet wird, mit dem Code eine genaue Kombination von Schnittstellen angeben kann, aber zumindest in .NET können sie einige starke Kopfschmerzen verursachen, da es keine anständigen gibt Möglichkeit, eine Sammlung von "Dingen anzugeben, die IFoo und IBar implementieren, aber ansonsten möglicherweise nichts gemeinsam haben".
Supercat
Generische Typparameter können mit Kriterien definiert werden, einschließlich der Implementierung mehrerer Schnittstellen. Sie haben jedoch Recht, dass Ausdrücke, die einen statischen Typ erfordern, normalerweise nicht die Angabe von mehr als einem unterstützen können. Wenn ein statischer Typ sowohl IFoo als auch IBar implementieren muss und Sie beide Schnittstellen steuern, ist es möglicherweise eine gute Idee, dies zu implementieren IBaz : IFoo, IBarund stattdessen zu fordern.
KeithS
Wenn Client-Code möglicherweise etwas benötigt, das als IFoound verwendet werden kann IBar, ist das Definieren eines Verbunds IFooBarmöglicherweise eine gute Idee. Wenn die Schnittstellen jedoch fein aufgeteilt sind, sind am Ende möglicherweise Dutzende unterschiedlicher Schnittstellentypen erforderlich. Berücksichtigen Sie die folgenden Funktionen, die Sammlungen möglicherweise haben: Aufzählen, Berichtsanzahl, Lesen des n-ten Elements, Schreiben des n-ten Elements, Einfügen vor dem n-ten Element, Löschen des n-ten Elements, Neues Element (Sammlung vergrößern und Index des neuen Bereichs zurückgeben) und Hinzufügen. Neun Methoden: ECRWIDNA. Ich könnte wahrscheinlich Dutzende von Typen beschreiben, die natürlich viele verschiedene Kombinationen unterstützen würden.
Supercat
Arrays würden beispielsweise ECRW unterstützen. Eine Arrayliste würde ECRWIDNA unterstützen. Eine thread-sichere Liste unterstützt möglicherweise ECRWNA [obwohl A im Allgemeinen nur zum Vorabfüllen der Liste nützlich ist]. Ein schreibgeschützter Array-Wrapper unterstützt möglicherweise ECR. Eine kovariante Listenschnittstelle könnte ECRD unterstützen. Eine nicht generische Schnittstelle kann typsichere Unterstützung C oder CD bieten. Wenn Swap eine Option wäre, könnten einige Typen CS unterstützen, nicht jedoch D (z. B. Arrays), während andere CDS unterstützen würden. Der Versuch, für jede notwendige Kombination von Fähigkeiten unterschiedliche Schnittstellentypen zu definieren, wäre ein Albtraum.
Supercat
Stellen Sie sich nun vor, Sie möchten eine Sammlung mit einem Objekt umschließen, das alles kann, was die Sammlung kann, aber jede Transaktion protokolliert. Wie viele Wrapper würde man brauchen? Wenn alle Sammlungen von einer gemeinsamen Schnittstelle geerbt würden, die Eigenschaften zur Identifizierung ihrer Fähigkeiten enthielt, würde ein Wrapper ausreichen. Wenn jedoch alle Schnittstellen unterschiedlich sind, würde man Dutzende benötigen.
Supercat