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 Deletable
Schnittstelle:
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?
object-oriented
interfaces
solid
single-responsibility
glenviewjeff
quelle
quelle
Antworten:
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 .
quelle
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.
quelle
IBaz : IFoo, IBar
und stattdessen zu fordern.IFoo
und verwendet werden kannIBar
, ist das Definieren eines VerbundsIFooBar
mö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.