Aus agiler Softwareentwicklung, Prinzipien, Mustern und Praktiken: Pearson New International Edition :
Manchmal überschneiden sich die von verschiedenen Gruppen von Clients aufgerufenen Methoden. Wenn die Überlappung gering ist, sollten die Schnittstellen für die Gruppen getrennt bleiben. Die gemeinsamen Funktionen sollten in allen überlappenden Schnittstellen deklariert werden. Die Serverklasse erbt die allgemeinen Funktionen von jeder dieser Schnittstellen, implementiert sie jedoch nur einmal.
Onkel Bob spricht über den Fall, wenn es geringfügige Überschneidungen gibt.
Was sollen wir tun, wenn es erhebliche Überschneidungen gibt?
Sagen wir, wir haben
Class UiInterface1;
Class UiInterface2;
Class UiInterface3;
Class UiIterface : public UiInterface1, public UiInterface2, public UiInterface3{};
Was sollen wir tun, wenn es signifikante Überschneidungen zwischen UiInterface1
und gibt UiInterface2
?
Antworten:
Casting
Dies wird mit ziemlicher Sicherheit eine vollständige Tangente an den Ansatz des zitierten Buches sein, aber eine Möglichkeit, sich besser an ISP anzupassen, besteht darin, eine Casting-Denkweise in einem zentralen Bereich Ihrer Codebasis mithilfe eines
QueryInterface
COM-ähnlichen Ansatzes zu entwickeln.Viele der Versuchungen, überlappende Schnittstellen in einem reinen Schnittstellenkontext zu entwerfen, entstehen häufig aus dem Wunsch, Schnittstellen "autark" zu machen, anstatt nur eine präzise, scharfschützenähnliche Verantwortung zu übernehmen.
Zum Beispiel mag es seltsam erscheinen, Client-Funktionen wie folgt zu entwerfen:
... sowie ziemlich hässlich / gefährlich, da wir die Verantwortung verlieren, fehleranfälliges Casting über diese Schnittstellen in den Client-Code durchzuführen und / oder dasselbe Objekt als Argument mehrmals an mehrere Parameter desselben zu übergeben Funktion. Daher möchten wir häufig eine verwässerte Schnittstelle entwerfen, die die Anliegen von
IParenting
undIPosition
an einem Ort konsolidiert , wieIGuiElement
oder so etwas, das dann anfällig für Überschneidungen mit den Anliegen von orthogonalen Schnittstellen wird, für die ebenfalls versucht sein wird, mehr Mitgliedsfunktionen zu haben der gleiche Grund der "Selbstversorgung".Verantwortlichkeiten mischen vs. Casting
Beim Entwerfen von Schnittstellen mit einer völlig destillierten, ultra-singulären Verantwortung besteht die Versuchung häufig darin, entweder ein Downcasting zu akzeptieren oder Schnittstellen zu konsolidieren, um mehrere Verantwortlichkeiten zu erfüllen (und daher sowohl ISP als auch SRP zu betreten).
Durch die Verwendung eines COM-
QueryInterface
ähnlichen Ansatzes (nur des Teils) spielen wir mit dem Downcasting-Ansatz, konsolidieren jedoch das Casting an einer zentralen Stelle in der Codebasis und können etwas Ähnliches tun:... natürlich hoffentlich mit typsicheren Wrappern und allem, was Sie zentral erstellen können, um etwas sichereres als rohe Zeiger zu erhalten.
Dadurch wird die Versuchung, überlappende Schnittstellen zu entwerfen, häufig auf das absolute Minimum reduziert. Es ermöglicht Ihnen, Schnittstellen mit sehr einzigartigen Verantwortlichkeiten (manchmal nur eine Mitgliedsfunktion im Inneren) zu entwerfen, die Sie mischen und anpassen können, ohne sich um den ISP kümmern zu müssen, und die Flexibilität der Pseudo-Enten-Eingabe zur Laufzeit in C ++ zu erhalten (obwohl natürlich mit den Kompromiss von Laufzeitstrafen, um Objekte abzufragen, um festzustellen, ob sie eine bestimmte Schnittstelle unterstützen). Der Laufzeitteil kann beispielsweise in einer Einstellung mit einem Software Development Kit wichtig sein, in der die Funktionen nicht über die Informationen zur Kompilierungszeit von Plugins verfügen, die diese Schnittstellen im Voraus implementieren.
Vorlagen
Wenn Vorlagen eine Möglichkeit sind (wir haben die erforderlichen Informationen zur Kompilierungszeit im Voraus, die nicht verloren gehen, wenn wir ein Objekt erreichen, dh), können wir dies einfach tun:
... in einem solchen Fall
parent
müsste die Methode natürlich denselbenEntity
Typ zurückgeben. In diesem Fall möchten wir wahrscheinlich Schnittstellen direkt vermeiden (da sie häufig Typinformationen verlieren möchten, um mit Basiszeigern zu arbeiten).Entity-Component-System
Wenn Sie den COM-ähnlichen Ansatz unter dem Gesichtspunkt der Flexibilität oder Leistung weiter verfolgen, erhalten Sie häufig ein System mit Entitätskomponenten, das den in der Branche geltenden Spiele-Engines ähnelt. An diesem Punkt werden Sie völlig senkrecht zu vielen objektorientierten Ansätzen gehen, aber ECS könnte auf das GUI-Design anwendbar sein (ein Ort, an dem ich ECS außerhalb eines szenenorientierten Fokus in Betracht gezogen habe, es aber zu spät in Betracht gezogen habe sich für einen COM-ähnlichen Ansatz zu entscheiden, um es dort zu versuchen).
Beachten Sie, dass diese Lösung im COM-Stil in Bezug auf GUI-Toolkit-Designs vollständig verfügbar ist und ECS sogar noch mehr, sodass sie nicht durch viele Ressourcen unterstützt wird. Auf diese Weise können Sie jedoch die Versuchungen, Schnittstellen mit überlappenden Verantwortlichkeiten zu entwerfen, auf ein absolutes Minimum reduzieren, was häufig zu einem Problem führt.
Pragmatischer Ansatz
Die Alternative besteht natürlich darin, Ihre Wache ein wenig zu entspannen oder Schnittstellen auf granularer Ebene zu entwerfen und sie dann zu erben, um gröbere Schnittstellen zu erstellen, die Sie verwenden, z. B.
IPositionPlusParenting
die von beidenIPosition
und abgeleitet sindIParenting
(hoffentlich mit einem besseren Namen als dem). Bei reinen Schnittstellen sollte der ISP nicht so stark verletzt werden wie bei den üblicherweise angewendeten monolithischen, tief hierarchischen Ansätzen (Qt, MFC usw.), bei denen in der Dokumentation häufig die Notwendigkeit besteht, irrelevante Mitglieder zu verbergen, da ISP bei diesen Arten übermäßig häufig verletzt wird Ein pragmatischer Ansatz könnte hier und da einfach eine gewisse Überlappung akzeptieren. Diese Art von COM-Ansatz vermeidet jedoch die Notwendigkeit, konsolidierte Schnittstellen für jede Kombination zu erstellen, die Sie jemals verwenden werden. Das Problem der "Selbstversorgung" wird in solchen Fällen vollständig beseitigt, und dies beseitigt häufig die ultimative Versuchung, Schnittstellen mit überlappenden Verantwortlichkeiten zu entwerfen, die sowohl mit SRP als auch mit ISP kämpfen möchten.quelle
Dies ist eine Entscheidung, die Sie von Fall zu Fall treffen müssen.
Denken Sie zunächst daran, dass SOLID-Prinzipien genau das sind ... Prinzipien. Das sind keine Regeln. Sie sind keine Silberkugel. Sie sind nur Prinzipien. Das soll ihre Bedeutung nicht beeinträchtigen, Sie sollten sich immer dazu neigen, ihnen zu folgen. Aber in der Sekunde, in der sie Schmerzen verursachen, sollten Sie sie loswerden, bis Sie sie brauchen.
Denken Sie vor diesem Hintergrund darüber nach, warum Sie Ihre Schnittstellen überhaupt trennen. Die Idee einer Schnittstelle lautet: "Wenn für diesen konsumierenden Code eine Reihe von Methoden für die konsumierte Klasse implementiert werden muss, muss ein Vertrag für die Implementierung geschlossen werden: Wenn Sie mir ein Objekt mit dieser Schnittstelle zur Verfügung stellen, kann ich arbeiten." damit."
Der Zweck des ISP besteht darin, zu sagen: "Wenn der von mir benötigte Vertrag nur eine Teilmenge einer vorhandenen Schnittstelle ist, sollte ich die vorhandene Schnittstelle nicht für zukünftige Klassen erzwingen, die möglicherweise an meine Methode übergeben werden."
Betrachten Sie den folgenden Code:
Jetzt haben wir eine Situation, in der, wenn wir ein neues Objekt an ConsumeX übergeben möchten, es X () und Y () implementieren muss, um dem Vertrag zu entsprechen.
Sollten wir jetzt den Code ändern, um wie im nächsten Beispiel auszusehen?
ISP schlägt vor, dass wir sollten, also sollten wir uns zu dieser Entscheidung neigen. Aber ohne Kontext ist es schwer, sicher zu sein. Ist es wahrscheinlich, dass wir A und B erweitern werden? Ist es wahrscheinlich, dass sie sich unabhängig voneinander verlängern? Ist es wahrscheinlich, dass B jemals Methoden implementiert, die A nicht benötigt? (Wenn nicht, können wir A von B ableiten lassen.)
Dies ist das Urteil, das Sie fällen müssen. Und wenn Sie wirklich nicht genug Informationen haben, um diesen Anruf zu tätigen, sollten Sie wahrscheinlich die einfachste Option wählen, die möglicherweise der erste Code ist.
Warum? Weil es einfach ist, Ihre Meinung später zu ändern. Wenn Sie diese neue Klasse benötigen, erstellen Sie einfach eine neue Schnittstelle und implementieren Sie beide in Ihrer alten Klasse.
quelle