Beim Lesen von Artikeln über ISP scheint es zwei widersprüchliche Definitionen von ISP zu geben:
Nach der ersten Definition (siehe 1 , 2 , 3 ) sollten ISP-Klassen, die die Schnittstelle implementieren, nicht gezwungen werden, Funktionen zu implementieren, die sie nicht benötigen. Also fette SchnittstelleIFat
interface IFat
{
void A();
void B();
void C();
void D();
}
class MyClass: IFat
{ ... }
sollte in kleinere Schnittstellen ISmall_1
und aufgeteilt werdenISmall_2
interface ISmall_1
{
void A();
void B();
}
interface ISmall_2
{
void C();
void D();
}
class MyClass:ISmall_2
{ ... }
da auf diese Weise meiner MyClass
Lage ist, nur die Methoden zu implementieren , es braucht ( D()
und C()
), ohne auch Dummy - Implementierungen für bietet gezwungen werden A()
, B()
und C()
:
Gemäß der zweiten Definition (siehe 1 , 2 , Antwort von Nazar Merza ) gibt der ISP an, dass beim MyClient
Aufrufen MyService
von Methoden MyService
nicht berücksichtigt werden sollte, welche Methoden er nicht benötigt. Mit anderen Worten, wenn MyClient
nur die Funktionalität von C()
und benötigt wird D()
, dann statt
class MyService
{
public void A();
public void B();
public void C();
public void D();
}
/*client code*/
MyService service = ...;
service.C();
service.D();
Wir sollten MyService's
Methoden in kundenspezifische Schnittstellen unterteilen:
public interface ISmall_1
{
void A();
void B();
}
public interface ISmall_2
{
void C();
void D();
}
class MyService:ISmall_1, ISmall_2
{ ... }
/*client code*/
ISmall_2 service = ...;
service.C();
service.D();
Daher besteht das Ziel von ISP in der ersteren Definition darin, " das Leben von Klassen, die die IFat-Schnittstelle implementieren, zu vereinfachen ", während das Ziel von ISP darin besteht, " das Leben von Clients, die Methoden von MyService aufrufen, zu vereinfachen ".
Welche der beiden unterschiedlichen Definitionen von ISP ist tatsächlich korrekt?
@MARJAN VENEMA
1.
Wenn Sie also IFat in eine kleinere Schnittstelle aufteilen, sollten Sie anhand der Kohäsion der Mitglieder entscheiden, welche Methoden in welcher ISmall-Schnittstelle landen.
Während es sinnvoll ist, zusammenhängende Methoden innerhalb derselben Schnittstelle zu platzieren, dachte ich, dass bei einem ISP-Muster die Bedürfnisse des Kunden Vorrang vor der "Kohäsivität" einer Schnittstelle haben. Mit anderen Worten, ich dachte, mit dem ISP sollten wir die Methoden, die von bestimmten Clients benötigt werden, innerhalb derselben Schnittstelle zusammenfassen, auch wenn dies bedeutet, dass diese Methoden aus Gründen der Kohäsivität auch innerhalb derselben Schnittstelle weggelassen werden sollten.
Wenn es also viele Kunden waren , die sich immer nur auf Anruf benötigt wird CutGreens
, aber auch nicht GrillMeat
, dann ISP Muster haften sollten wir nur setzen CutGreens
innen ICook
, aber auch nicht GrillMeat
, auch wenn die beiden Methoden sind hochkohäsiv ?!
2.
Ich denke, Ihre Verwirrung rührt von der versteckten Annahme in der ersten Definition her: Die implementierenden Klassen folgen bereits dem Prinzip der einheitlichen Verantwortung.
Beziehen Sie sich mit "Implementieren von Klassen, die SRP nicht folgen" auf Klassen, die implementieren, IFat
oder auf Klassen, die ISmall_1
/ implementieren ISmall_2
? Ich nehme an, Sie beziehen sich auf Klassen, die implementieren IFat
? Wenn ja, warum nehmen Sie an, dass sie SRP nicht bereits folgen?
Vielen Dank
quelle
Antworten:
Beide sind richtig
So wie ich es lese, besteht der Zweck von ISP (Interface Segregation Principle) darin, Schnittstellen klein und fokussiert zu halten: Alle Schnittstellenmitglieder sollten einen sehr hohen Zusammenhalt haben. Beide Definitionen sollen "Alleskönner" -Schnittstellen vermeiden.
Schnittstellentrennung und SRP (Single Responsibility Principle) verfolgen dasselbe Ziel: Sicherstellung kleiner, sehr zusammenhängender Softwarekomponenten. Sie ergänzen sich. Die Schnittstellentrennung stellt sicher, dass die Schnittstellen klein, fokussiert und in hohem Maße zusammenhängend sind. Die Befolgung des Grundsatzes der einheitlichen Verantwortung stellt sicher, dass die Klassen klein, konzentriert und in hohem Maße zusammenhängend sind.
Die erste Definition, die Sie erwähnen, konzentriert sich auf Implementierer, die zweite auf Clients. Was ich im Gegensatz zu @ user61852 als Benutzer / Aufrufer der Schnittstelle und nicht als Implementierer betrachte.
Ich denke, Ihre Verwirrung rührt von der versteckten Annahme in der ersten Definition her: Die implementierenden Klassen folgen bereits dem Prinzip der einheitlichen Verantwortung.
Für mich ist die zweite Definition mit den Clients als Aufrufern der Schnittstelle ein besserer Weg, um zum beabsichtigten Ziel zu gelangen.
Trennung
In Ihrer Frage stellen Sie fest:
Aber das stellt die Welt auf den Kopf.
Wenn Sie also
IFat
in kleinere Benutzeroberflächen aufteilen ,ISmall
sollten Sie anhand der Kohäsion der Mitglieder entscheiden , welche Methoden in welcher Benutzeroberfläche landen .Betrachten Sie diese Schnittstelle:
Welche Methoden würden Sie einsetzen
ICook
und warum? Würden Sie mitCleanSink
zusammenarbeiten,GrillMeat
nur weil Sie zufällig eine Klasse haben, die genau das und ein paar andere Dinge tut, aber nichts mit einer der anderen Methoden zu tun hat? Oder würden Sie es in zwei zusammenhängende Schnittstellen aufteilen, wie zum Beispiel:Hinweis zur Schnittstellendeklaration
Eine Schnittstellendefinition sollte vorzugsweise in einer separaten Einheit für sich sein, aber wenn sie unbedingt mit einem Aufrufer oder Implementierer zusammenleben muss, sollte sie wirklich mit dem Aufrufer sein. Andernfalls erhält der Aufrufer eine unmittelbare Abhängigkeit vom Implementierer, was den Zweck von Schnittstellen insgesamt zunichte macht. Siehe auch: Deklarieren der Schnittstelle in derselben Datei wie die Basisklasse. Ist dies eine gute Vorgehensweise? auf Programmierer und warum sollten wir Schnittstellen mit Klassen platzieren, die sie verwenden, anstatt solche, die sie implementieren? auf StackOverflow.
quelle
ICook
statt vom Typ sindSomeCookImplementor
, wie dies bei DIP-Mandaten der Fall ist muss sich nicht darauf verlassenSomeCookImplementor
.Sie verwechseln das Wort "Client", wie es in den Dokumenten der Vierergruppe verwendet wird, mit einem "Client", wie es beim Verbraucher einer Dienstleistung verwendet wird.
Ein "Client", wie er in den Definitionen der Vierergruppe vorgesehen ist, ist eine Klasse, die eine Schnittstelle implementiert. Wenn Klasse A Schnittstelle B implementiert, dann sagen sie, dass A ein Client von B ist. Andernfalls sollte der Ausdruck "Clients sollten nicht gezwungen werden, Schnittstellen zu implementieren, die sie nicht verwenden" nicht sinnvoll, da "Clients" (wie bei Verbrauchern) nicht funktionieren nichts implementieren. Der Satz ist nur dann sinnvoll, wenn Sie "client" als "implementor" sehen.
Wenn "client" eine Klasse bedeutet, die die Methoden einer anderen Klasse "verbraucht" (aufruft), die die große Schnittstelle implementiert, dann würde es ausreichen, die beiden Methoden aufzurufen, die Sie interessieren, und den Rest zu ignorieren, um Sie vom Rest von zu entkoppeln die Methoden, die Sie nicht verwenden.
Der Geist des Prinzips besteht darin, zu vermeiden, dass der "Client" (die Klasse, die die Schnittstelle implementiert) Dummy-Methoden implementieren muss, um die gesamte Schnittstelle zu erfüllen, wenn es nur um eine Reihe von Methoden geht, die miteinander in Beziehung stehen.
Es wird auch angestrebt, den Kopplungsaufwand so gering wie möglich zu halten, damit an einem Ort vorgenommene Änderungen weniger Auswirkungen haben. Durch die Trennung der Schnittstellen reduzieren Sie die Kopplung.
Dieses Problem tritt auf, wenn die Schnittstelle zu viel tut und Methoden hat, die in mehrere Schnittstellen anstatt nur einer aufgeteilt werden sollten.
Ihre beiden Codebeispiele sind in Ordnung . Es ist nur so, dass Sie in der zweiten davon ausgehen, dass "client" "eine Klasse, die die von einer anderen Klasse angebotenen Dienste / Methoden nutzt / aufruft".
Ich finde keine Widersprüche in den Begriffen, die in den drei von Ihnen angegebenen Links erläutert wurden.
Stellen Sie in SOLID talk nur klar, dass "client" Implementierer ist.
quelle
Beim ISP geht es darum, den Client davon abzuhalten, mehr über den Dienst zu wissen, als er wissen muss (z. B. um ihn vor nicht zusammenhängenden Änderungen zu schützen). Ihre zweite Definition ist korrekt. Meiner Meinung nach schlägt nur einer dieser drei Artikel etwas anderes vor ( der erste ) und es ist einfach falsch. (Edit: Nein, nicht falsch, nur irreführend.)
Die erste Definition ist viel enger mit LSP verbunden.
quelle