Ich habe ein Modul, sagen Sie "M", das ein paar Clients hat, sagen Sie "C1", "C2", "C3". Ich möchte den Namespace des Moduls M, dh die Deklarationen der von ihm verfügbar gemachten APIs und Daten, so in Header-Dateien unterteilen, dass:
- Für jeden Client sind nur die erforderlichen Daten und APIs sichtbar. Der restliche Namespace des Moduls ist dem Client verborgen, dh es wird das Prinzip der Schnittstellensegregation befolgt .
- Eine Deklaration wird in mehreren Header-Dateien nicht wiederholt, verstößt also nicht gegen DRY .
- Modul M hat keine Abhängigkeiten von seinen Clients.
- Ein Kunde bleibt von den Änderungen unberührt, die an Teilen des Moduls M vorgenommen werden, die nicht von ihm verwendet werden.
- Bestehende Kunden bleiben von der Hinzufügung (oder Löschung) weiterer Kunden unberührt.
Derzeit beschäftige ich mich damit, indem ich den Namespace des Moduls in Abhängigkeit von den Anforderungen seiner Kunden teile. In der folgenden Abbildung werden beispielsweise die verschiedenen Teile des Namensraums des Moduls angezeigt, die von seinen drei Clients benötigt werden. Kundenanforderungen überschneiden sich. Der Namespace des Moduls ist in 4 separate Header-Dateien unterteilt: 'a', '1', '2' und '3' .
Dies verletzt jedoch einige der oben genannten Anforderungen, dh R3 und R5. Anforderung 3 wird verletzt, da diese Partitionierung von der Art der Clients abhängt. auch beim Hinzufügen eines neuen Clients ändert sich diese Partitionierung und verstößt gegen Anforderung 5. Wie auf der rechten Seite des obigen Bildes zu sehen ist, ist der Namespace des Moduls beim Hinzufügen eines neuen Clients jetzt in 7 Header-Dateien unterteilt - 'a ',' b ',' c ',' 1 ',' 2 * ',' 3 * 'und' 4 ' . Die Header-Dateien, die für 2 der älteren Clients bestimmt sind, ändern sich und lösen dadurch deren Neuerstellung aus.
Gibt es eine Möglichkeit, die Schnittstellentrennung in C auf nicht konstruierte Weise zu erreichen?
Wenn ja, wie würden Sie mit dem obigen Beispiel umgehen?
Ich stelle mir eine unwirkliche hypothetische Lösung vor:
Das Modul hat eine fette Header-Datei, die den gesamten Namespace abdeckt. Diese Header-Datei ist wie eine Wikipedia-Seite in adressierbare Abschnitte und Unterabschnitte unterteilt. Jeder Client hat dann eine spezielle Header-Datei, die auf ihn zugeschnitten ist. Die kundenspezifischen Header-Dateien sind lediglich eine Liste von Hyperlinks zu den Abschnitten / Unterabschnitten der Fat-Header-Datei. Das Build-System muss eine clientspezifische Header-Datei als "geändert" erkennen, wenn einer der Abschnitte, auf die es im Header des Moduls verweist, geändert wird.
quelle
struct
verwenden Sie in C, wenn Sie eine Schnittstelle benötigen. Zugegeben, Methoden sind etwas schwierig. Das könnte Sie interessieren: cs.rit.edu/~ats/books/ooc.pdfstruct
undfunction pointers
.Antworten:
Die Schnittstellentrennung sollte im Allgemeinen nicht auf Kundenanforderungen basieren. Sie sollten den gesamten Ansatz ändern, um dies zu erreichen. Ich würde sagen, modularisieren Sie die Schnittstelle, indem Sie die Features in zusammenhängende Gruppen gruppieren. Das heißt, die Gruppierung basiert auf der Kohärenz der Features selbst und nicht auf den Kundenanforderungen. In diesem Fall verfügen Sie über eine Reihe von Schnittstellen, I1, I2 usw. Der Client C1 kann I2 allein verwenden. Client C2 kann I1 und I5 usw. verwenden. Beachten Sie, dass es kein Problem ist, wenn ein Client mehr als ein Ii verwendet. Wenn Sie die Schnittstelle in zusammenhängende Module zerlegt haben, ist das der Kern der Sache.
Auch hier ist ISP nicht clientbasiert. Es geht darum, die Schnittstelle in kleinere Module zu zerlegen. Wenn dies ordnungsgemäß durchgeführt wird, wird auch sichergestellt, dass Clients mit so wenigen Funktionen wie nötig konfrontiert werden.
Mit diesem Ansatz können Ihre Kunden auf eine beliebige Anzahl aufgestockt werden, ohne dass Sie M davon betroffen sind. Jeder Client verwendet je nach Bedarf eine oder mehrere der Schnittstellen. Gibt es Fälle, in denen ein Client (C) beispielsweise I1 und I3 einschließen muss, aber nicht alle Funktionen dieser Schnittstellen nutzt? Ja, das ist kein Problem. Es werden nur die wenigsten Schnittstellen verwendet.
quelle
Das Prinzip der Schnittstellentrennung lautet:
Hier sind einige Fragen offen. Eins ist:
Wie klein?
Du sagst:
Ich nenne das manuelle Entenschreiben . Sie erstellen Schnittstellen, die nur die Anforderungen eines Clients offen legen. Das Prinzip der Schnittstellentrennung besteht nicht nur in der manuellen Eingabe von Enten.
Der ISP ist jedoch nicht nur ein Aufruf für "kohärente" Rollenschnittstellen, die wiederverwendet werden können. Kein "kohärentes" Design der Rollenschnittstelle kann perfekt verhindern, dass ein neuer Client mit eigenen Rollenanforderungen hinzugefügt wird.
ISP ist eine Möglichkeit, Clients von den Auswirkungen von Änderungen am Dienst zu isolieren. Damit sollte der Build schneller ausgeführt werden, wenn Sie Änderungen vornehmen. Sicher hat es andere Vorteile, wie nicht Kunden zu brechen, aber das war der Hauptpunkt. Wenn ich die
count()
Signatur der Services- Funktion ändere, ist es hilfreich, wenn Clients, die sie nicht verwendencount()
, nicht bearbeitet und neu kompiliert werden müssen.Dies ist der Grund, warum mir das Prinzip der Schnittstellentrennung am Herzen liegt. Es ist nicht etwas, was ich für wichtig halte. Es löst ein echtes Problem.
So sollte die Art und Weise, wie es angewendet werden sollte, ein Problem für Sie lösen. Es gibt keine hirntote Möglichkeit, ISP anzuwenden, die nicht mit dem richtigen Beispiel für eine notwendige Änderung besiegt werden kann. Sie sollten sich ansehen, wie sich das System ändert, und Entscheidungen treffen, die die Dinge beruhigen. Lassen Sie uns die Optionen untersuchen.
Stellen Sie sich zunächst die Frage, ob es derzeit schwierig ist, Änderungen an der Service-Oberfläche vorzunehmen. Wenn nicht, geh nach draußen und spiele, bis du dich beruhigt hast. Dies ist keine intellektuelle Übung. Bitte stellen Sie sicher, dass die Heilung nicht schlimmer ist als die Krankheit.
Wenn viele Clients dieselbe Teilmenge von Funktionen verwenden, spricht dies für "kohärente" wiederverwendbare Schnittstellen. Die Teilmenge konzentriert sich wahrscheinlich auf eine Idee, die wir uns als die Rolle vorstellen können, die der Service für den Kunden bereitstellt. Es ist schön, wenn das funktioniert. Das funktioniert nicht immer.
Wenn viele Clients unterschiedliche Teilmengen von Funktionen verwenden, verwendet der Client den Service möglicherweise tatsächlich über mehrere Rollen. Das ist in Ordnung, aber es macht die Rollen schwer zu sehen. Finde sie und versuche sie auseinander zu ziehen. Das könnte uns in Fall 1 zurückversetzen. Der Client nutzt den Service einfach über mehr als eine Schnittstelle. Bitte fange nicht an, den Dienst zu besetzen. Wenn überhaupt, würde dies bedeuten, dass der Service mehr als einmal an den Kunden weitergegeben wird. Das funktioniert, aber ich frage mich, ob der Service kein großer Schlammballen ist, der aufgebrochen werden muss.
Wenn viele Clients unterschiedliche Teilmengen verwenden, Sie jedoch keine Rollen sehen, bei denen die Clients möglicherweise mehr als eine Rolle verwenden, gibt es nichts Besseres als das Eingeben von Enten für die Gestaltung Ihrer Benutzeroberflächen. Auf diese Weise wird beim Entwerfen der Schnittstellen sichergestellt, dass der Client nicht einer einzigen Funktion ausgesetzt ist, die er nicht verwendet. Es wird jedoch fast garantiert, dass beim Hinzufügen eines neuen Clients immer eine neue Schnittstelle hinzugefügt wird, die die Serviceimplementierung nicht kennen muss darüber die Schnittstelle, die die Rollenschnittstellen aggregiert. Wir haben einfach einen Schmerz gegen einen anderen getauscht.
Wenn viele Clients unterschiedliche Teilmengen verwenden und sich überlappen, wird erwartet, dass neue Clients hinzugefügt werden, die unvorhersehbare Teilmengen benötigen, und Sie nicht bereit sind, den Dienst aufzulösen, sondern eine funktionalere Lösung in Betracht ziehen. Da die ersten beiden Optionen nicht funktionierten und Sie sich wirklich an einem schlechten Ort befinden, an dem nichts einem Muster folgt und weitere Änderungen bevorstehen, sollten Sie erwägen, jeder Funktion eine eigene Benutzeroberfläche bereitzustellen. Das bedeutet nicht, dass der ISP gescheitert ist. Wenn etwas fehlschlug, war es das objektorientierte Paradigma. Einzelmethodenschnittstellen folgen im Extremfall ISP. Es ist ein gutes Stück Tastatur-Eingabe, aber Sie werden feststellen, dass die Schnittstellen dadurch plötzlich wiederverwendbar werden. Stellen Sie sicher, dass es keine gibt.
Es stellt sich also heraus, dass sie tatsächlich sehr klein werden können.
Ich habe diese Frage als Herausforderung angesehen, um ISP in den extremsten Fällen anzuwenden. Bedenken Sie jedoch, dass Extreme am besten vermieden werden. In einem gut durchdachten Design, das andere SOLID-Prinzipien anwendet, treten diese Probleme normalerweise nicht auf oder spielen kaum eine Rolle.
Eine weitere unbeantwortete Frage lautet:
Wem gehören diese Schnittstellen?
Immer wieder sehe ich Schnittstellen, die mit einer "Bibliotheks" -Mentalität entworfen wurden. Wir haben uns alle der Affen-See-Affen-Do-Codierung schuldig gemacht, bei der Sie nur etwas tun, weil Sie es so gesehen haben. Wir sind an der gleichen Sache mit Schnittstellen schuld.
Wenn ich mir ein Interface anschaue, das für eine Klasse in einer Bibliothek entworfen wurde, habe ich gedacht: Oh, diese Leute sind Profis. Dies muss der richtige Weg sein, um eine Schnittstelle zu erstellen. Was ich nicht verstanden habe, ist, dass eine Bibliotheksgrenze ihre eigenen Bedürfnisse und Probleme hat. Zum einen kennt eine Bibliothek das Design ihrer Kunden überhaupt nicht. Nicht jede Grenze ist gleich. Und manchmal kann sogar dieselbe Grenze auf unterschiedliche Weise überschritten werden.
Es gibt zwei einfache Möglichkeiten, das Interface-Design zu betrachten:
Service-Schnittstelle. Einige Leute entwerfen jede Schnittstelle, um alles darzustellen, was ein Dienst tun kann. Sie können sogar Refactoring-Optionen in IDEs finden, die eine Schnittstelle für Sie schreiben, die die von Ihnen angegebene Klasse verwendet.
Client-Schnittstelle. Der ISP scheint zu argumentieren, dass dies richtig und der Service-Besitz falsch ist. Sie sollten jede Schnittstelle im Hinblick auf die Kundenanforderungen aufteilen. Da der Client die Schnittstelle besitzt, sollte er sie definieren.
Also, wer hat Recht?
Plugins berücksichtigen:
Wem gehören die Schnittstellen hier? Die Kunden? Die Dienste?
Es stellt sich heraus, beide.
Die Farben hier sind Schichten. Die rote Schicht (rechts) soll nichts über die grüne Schicht (links) wissen. Die grüne Schicht kann geändert oder ersetzt werden, ohne die rote Schicht zu berühren. Auf diese Weise kann eine beliebige grüne Schicht in die rote Schicht eingesteckt werden.
Ich mag es zu wissen, was darüber wissen soll und was nicht. Für mich ist "Was weiß wovon?" Die wichtigste architektonische Frage.
Lassen Sie uns einige Vokabeln klarstellen:
Ein Client ist etwas, das verwendet.
Ein Service wird genutzt.
Interactor
zufällig beides.Laut ISP werden Schnittstellen für Clients getrennt. Gut, lassen Sie uns das hier anwenden:
Presenter
(ein Dienst) sollte derOutput Port <I>
Schnittstelle nicht diktieren . Die Schnittstelle sollte auf dieInteractor
Anforderungen (hier als Client) beschränkt werden. Das bedeutet, dass sich die Schnittstelle, die über das InternetInteractor
und den Internetdienstanbieter Bescheid weiß, damit ändern muss. Und das ist in Ordnung.Interactor
(hier als Dienst agierend) sollte derInput Port <I>
Schnittstelle nicht diktieren . Die Schnittstelle sollte auf dieController
Bedürfnisse (eines Kunden) beschränkt werden. Das bedeutet, dass sich die Schnittstelle, die über das InternetController
und den Internetdienstanbieter Bescheid weiß, damit ändern muss. Und das ist nicht in Ordnung.Die zweite ist nicht in Ordnung, weil die rote Schicht nichts über die grüne Schicht wissen soll. Ist ISP also falsch? Na irgendwie. Kein Prinzip ist absolut. Dies ist ein Fall, bei dem sich herausstellt, dass die Benutzeroberfläche, die alles zeigt, was der Service kann, in Ordnung ist.
Zumindest haben sie recht, wenn
Interactor
sie nichts anderes tun, als dies für den Anwendungsfall erforderlich ist. Wenn dieInteractor
Dinge für andere Anwendungsfälle erledigt werden, gibt es keinen Grund, warum diesInput Port <I>
bekannt sein sollte. Ich bin mir nicht sicher, warum ich michInteractor
nicht nur auf einen Anwendungsfall konzentrieren kann. Dies ist kein Problem, aber es passiert etwas.Aber das
input port <I>
Interface kann sich einfach nicht demController
Client unterordnen und muss ein echtes Plugin sein. Dies ist eine Bibliotheksgrenze. Ein völlig anderer Programmierladen könnte die grüne Schicht Jahre nach der Veröffentlichung der roten Schicht schreiben.Wenn Sie eine Bibliotheksgrenze überschreiten und das Gefühl haben, ISP anwenden zu müssen, obwohl Ihnen die Schnittstelle auf der anderen Seite nicht gehört, müssen Sie einen Weg finden, die Schnittstelle einzugrenzen, ohne sie zu ändern.
Eine Möglichkeit, dies zu erreichen, ist ein Adapter. Setzen Sie es zwischen Clients wie
Controler
und derInput Port <I>
Schnittstelle. Der Adapter übernimmtInteractor
als einInput Port <I>
und die Arbeit delegiert.Controller
Über eine Rollenschnittstelle oder Schnittstellen, die der grünen Ebene gehören , werden jedoch nur die Anforderungen der Clients offengelegt. Der Adapter folgt nicht dem ISP selbst, sondern ermöglicht komplexeren KlassenController
, ISP zu genießen. Dies ist nützlich, wenn es weniger Adapter gibt, als solche ClientsController
verwenden, und wenn Sie sich in einer ungewöhnlichen Situation befinden, in der Sie eine Bibliotheksgrenze überschreiten und die Bibliothek trotz Veröffentlichung nicht aufhört, sich zu ändern. Ich schaue dich an Firefox. Jetzt zerstören diese Änderungen nur Ihre Adapter.Also, was bedeutet das? Es bedeutet ehrlich gesagt, dass Sie nicht genügend Informationen bereitgestellt haben, damit ich Ihnen sagen kann, was Sie tun sollten. Ich weiß nicht, ob Sie ein Problem haben, wenn Sie ISP nicht folgen. Ich weiß nicht, ob das Folgen nicht zu weiteren Problemen führen würde.
Ich weiß, dass Sie nach einem einfachen Leitsatz suchen. ISP versucht das zu sein. Aber es bleibt viel ungesagt. Ich glaube daran. Ja, bitte zwingen Sie Kunden nicht, sich ohne guten Grund auf Methoden zu verlassen, die sie nicht anwenden!
Wenn Sie einen guten Grund haben, z. B. das Entwerfen von Plugins, müssen Sie sich der Probleme bewusst sein, die nicht den Ursachen des Internetdienstanbieters entsprechen (es ist schwierig, Änderungen vorzunehmen, ohne die Clients zu beschädigen), und der Möglichkeiten, diese zu entschärfen (behalten
Interactor
oder sich zumindestInput Port <I>
auf einen Stall zu konzentrieren) Anwendungsfall).quelle
Also dieser Punkt:
Gibt auf, dass Sie gegen ein anderes wichtiges Prinzip verstoßen, nämlich YAGNI. Ich würde mich darum kümmern, wenn ich Hunderte von Kunden habe. Wenn Sie im Vorfeld über etwas nachdenken, stellt sich heraus, dass Sie für diesen Code keine zusätzlichen Clients haben, ist dies besser als der Zweck.
Zweite
Warum verwendet Ihr Code kein DI, keine Abhängigkeitsinversion, nichts, nichts in Ihrer Bibliothek sollte von Ihrer Client-Natur abhängen.
Schließlich scheint es, als bräuchten Sie eine zusätzliche Ebene unter Ihrem Code, um die Anforderungen für überlappende Inhalte zu erfüllen (DI, sodass Ihr Code für die Vorderseite nur von dieser zusätzlichen Ebene und Ihre Kunden nur von Ihrer Schnittstelle für die Vorderseite abhängt). Auf diese Weise schlagen Sie den DRY.
Das würdest du wirklich tun. Also machst du dasselbe Zeug, das du in deiner Modulebene unter einem anderen Modul verwendest. Auf diese Weise erreichen Sie mit der Schicht darunter:
Ja
Ja
Ja
Ja
quelle
In der Definition werden immer die gleichen Angaben wie in der Deklaration wiederholt. So funktioniert diese Sprache. Ebenfalls, Wiederholen einer Deklaration in mehreren Header-Dateien verletzt DRY nicht . Es ist eine ziemlich häufig verwendete Technik (zumindest in der Standardbibliothek).
Das Wiederholen der Dokumentation oder der Implementierung würde gegen DRY verstoßen .
Ich würde mich nicht damit beschäftigen, wenn der Client-Code nicht von mir geschrieben wird.
quelle
Ich lehne meine Verwirrung ab. Ihr praktisches Beispiel zieht jedoch eine Lösung in meinen Kopf. Wenn ich es in meine eigenen Worte fassen kann: Alle Partitionen im Modul
M
haben viele zu viele exklusive Beziehung zu jedem und allen Kunden.Beispielstruktur
Mh
Mc
In der Mc-Datei müssten Sie die #ifdefs nicht verwenden, da das, was Sie in die .c-Datei einfügen, die Client-Dateien nicht beeinflusst, solange die von den Client-Dateien verwendeten Funktionen definiert sind.
C1.c
C2.c
C3.c
Auch hier bin ich mir nicht sicher, ob Sie danach fragen. Also nimm es mit einem Körnchen Salz.
quelle
P1_init()
undP2_init()
?P1_init()
undP2_init()
womit verknüpft?_PREF_
zuletzt definierten ersetzt. Das_PREF_init()
liegt auchP1_init()
an der letzten #define-Anweisung. Dann setzt die nächste definiere Anweisung PREF gleich P2_ und erzeugt soP2_init()
.