Aber ist die draw () -Methode nicht sehr stark von der Benutzeroberfläche abhängig?
Aus pragmatischer Sicht muss ein Code in Ihrem System wissen, wie man so etwas zeichnet, Rectanglewenn dies für den Benutzer erforderlich ist. Und das wird sich irgendwann auf wirklich einfache Dinge wie das Rastern von Pixeln oder das Anzeigen von Inhalten in einer Konsole auswirken.
Aus Sicht der Kopplung stellt sich für mich die Frage, wer / was von dieser Art von Informationen abhängen soll und in welchem Detailgrad (wie abstrakt zB)?
Abstrakte Zeichen- / Renderfunktionen
Denn wenn der übergeordnete Zeichnungscode nur von etwas sehr Abstraktem abhängt, funktioniert diese Abstraktion möglicherweise (durch Substitution konkreter Implementierungen) auf allen Plattformen, auf die Sie abzielen möchten. Als konstruiertes Beispiel IDrawerkönnte eine sehr abstrakte Schnittstelle in der Lage sein, sowohl in Konsolen- als auch in GUI-APIs implementiert zu werden, um Dinge wie Plotformen auszuführen (die Konsolenimplementierung könnte die Konsole wie ein "Bild" von 80 x N mit ASCII-Grafik behandeln). Natürlich ist das ein erfundenes Beispiel, da Sie eine Konsolenausgabe normalerweise nicht wie einen Bild- / Bildpuffer behandeln möchten. In der Regel erfordern die meisten Benutzeranforderungen mehr textbasierte Interaktionen in Konsolen.
Eine andere Überlegung ist, wie einfach es ist, eine stabile Abstraktion zu entwerfen. Weil es einfach sein könnte, wenn Sie nur moderne GUI-APIs als Ziel verwenden, um die grundlegenden Funktionen zum Zeichnen von Formen wie Zeichnen von Linien, Rechtecken, Pfaden, Text und ähnlichen Dingen zu abstrahieren (nur einfache 2D-Rasterung einer begrenzten Menge von Grundelementen). , mit einer abstrakten Schnittstelle, die mit geringem Aufwand über verschiedene Subtypen einfach für alle implementiert werden kann. Wenn Sie eine solche Abstraktion effektiv entwerfen und auf allen Zielplattformen implementieren können, dann würde ich sagen, dass es für eine Form- oder GUI-Steuerung oder was auch immer ein weitaus geringeres Übel ist, wenn Sie wissen, wie Sie sich selbst mit einer solchen zeichnen Abstraktion.
Angenommen, Sie versuchen, die blutigen Details zu beseitigen, die zwischen einer Playstation Portable, einem iPhone, einer XBox One und einem leistungsstarken Gaming-PC variieren, während Sie die modernsten Echtzeit-3D-Rendering- / Shading-Techniken für jede einzelne verwenden müssen . In diesem Fall ist es fast sicher, dass der Versuch, eine einzige abstrakte Benutzeroberfläche zu entwickeln, um die Rendering-Details zu abstrahieren, wenn die zugrunde liegenden Hardwarefunktionen und APIs so stark variieren, zu einem enormen Zeitaufwand beim Entwerfen und erneuten Entwerfen führt Entdeckungen und ebenso eine Lösung mit dem kleinsten gemeinsamen Nenner, bei der die volle Einzigartigkeit und Leistungsfähigkeit der zugrunde liegenden Hardware nicht ausgenutzt wird.
Abhängigkeiten fließen in Richtung stabiler, "einfacher" Designs
Auf meinem Gebiet bin ich in diesem letzten Szenario. Wir zielen auf viele verschiedene Hardware mit radikal unterschiedlichen zugrunde liegenden Fähigkeiten und APIs ab, und der Versuch, eine Rendering- / Zeichnungsabstraktion zu entwickeln, um sie alle zu beherrschen, ist grenzenlos hoffnungslos (wir könnten weltberühmt werden, wenn wir das nur so effektiv tun, als wäre es ein Spiel Wechsler in der Industrie). Also das letzte , was ich in meinem Fall möge wie der analogical ist Shapeoder Modeloder Particle Emitterdas weiß , wie man sich ziehen, auch wenn es , dass in der höchsten Ebene zeichnen ausdrückt und abstrakteste Weise möglich ...
... weil diese Abstraktionen zu schwer zu entwerfen sind und wenn es schwierig ist, ein Design zu erhalten und alles davon abhängt, ist dies ein Rezept für die kostspieligsten zentralen Designänderungen, die alles davon abhängig kräuseln und zerbrechen. Das Letzte, was Sie möchten, ist, dass die Abhängigkeiten in Ihren Systemen zu stark in Richtung abstrakter Designs fließen, um korrekt zu werden (zu stark, um ohne aufdringliche Änderungen stabilisiert zu werden).
Schwierig hängt von leicht ab, nicht leicht hängt von schwer ab
Stattdessen lassen wir die Abhängigkeiten in Richtung einfach zu gestaltender Dinge fließen. Es ist viel einfacher, ein abstraktes "Modell" zu entwerfen, das sich nur auf das Speichern von Dingen wie Polygonen und Materialien konzentriert, und dieses Design korrekt zu gestalten, als einen abstrakten "Renderer" zu entwerfen, der effektiv (durch austauschbare konkrete Subtypen) für Service-Zeichnungen implementiert werden kann fordert einheitlich Hardware an, die so unterschiedlich ist wie eine PSP von einem PC.

Wir kehren also die Abhängigkeiten von den Dingen um, die schwer zu entwerfen sind. Anstatt abstrakte Modelle dazu zu bringen, sich auf ein abstraktes Renderer-Design zu beziehen, von dem sie alle abhängig sind (und ihre Implementierungen zu unterbrechen, wenn sich dieses Design ändert), haben wir stattdessen einen abstrakten Renderer, der weiß, wie jedes abstrakte Objekt in unserer Szene gezeichnet wird ( Modelle, Partikelemitter usw.), und so können wir dann einen OpenGL-Renderer-Subtyp für PCs wie RendererGl, einen anderen für PSPs wie RendererPsp, einen anderen für Mobiltelefone usw. implementieren . In diesem Fall fließen die Abhängigkeiten in Richtung stabiler Designs. Vom Renderer zu verschiedenen Arten von Objekten (Modellen, Partikeln, Texturen usw.) in unserer Szene, nicht umgekehrt.

- Ich verwende "Stabilität / Instabilität" in einem etwas anderen Sinne als die Metrik der afferenten / efferenten Kopplungen von Onkel Bob, die nach meinem Verständnis mehr die Schwierigkeit der Veränderung misst. Ich spreche mehr über die "Wahrscheinlichkeit, dass eine Änderung erforderlich ist", obwohl seine Stabilitätsmetrik dort nützlich ist. Wenn "Wahrscheinlichkeit einer Änderung" proportional zu "Leichtigkeit einer Änderung" ist (zum Beispiel: Die Dinge, die Änderungen am wahrscheinlichsten erfordern, weisen die höchste Instabilität und afferente Kopplungen von Onkel Bobs Metrik auf), sind solche wahrscheinlichen Änderungen billig und nicht aufdringlich durchzuführen Sie müssen lediglich eine Implementierung ersetzen, ohne zentrale Designs zu berühren.
Wenn Sie versuchen, etwas auf einer zentralen Ebene Ihrer Codebasis zu abstrahieren, und es einfach zu schwierig ist, es zu entwerfen, anstatt hartnäckig gegen die Wände zu schlagen und ständig aufdringliche Änderungen daran vorzunehmen, die das Aktualisieren von 8.000 Quelldateien erfordern, weil es sich um solche handelt Mein erster Vorschlag ist, die Abhängigkeiten zu invertieren. Sehen Sie, ob Sie den Code so schreiben können, dass das, was so schwer zu entwerfen ist, von allem abhängt, was einfacher zu entwerfen ist, und nicht von den Dingen, die einfacher zu entwerfen sind, je nachdem, was so schwer zu entwerfen ist. Beachten Sie, dass es sich um Designs handelt (speziell um Schnittstellendesigns) und nicht um Implementierungen: Manchmal sind die Dinge einfach zu entwerfen und schwer zu implementieren. und manchmal sind Dinge schwer zu entwerfen, aber einfach zu implementieren. Abhängigkeiten fließen in Richtung Designs, daher sollte der Fokus nur darauf liegen, wie schwierig es ist, hier etwas zu entwerfen, um die Richtung zu bestimmen, in die Abhängigkeiten fließen.
Grundsatz der einheitlichen Verantwortung
Für mich ist SRP hier normalerweise nicht so interessant (obwohl es vom Kontext abhängt). Ich meine, es gibt eine Gratwanderung beim Entwerfen von Dingen, die eindeutig und wartbar sind, aber Ihre ShapeObjekte müssen möglicherweise detailliertere Informationen anzeigen, wenn sie beispielsweise nicht wissen, wie sie sich selbst zeichnen sollen, und es gibt möglicherweise nicht viele sinnvolle Dinge dafür Mit einer Form in einem bestimmten Verwendungskontext zu tun, als sie zu konstruieren und zu zeichnen. Es gibt Kompromisse mit so gut wie allem, und es hängt nicht mit SRP zusammen, das die Dinge darauf aufmerksam machen kann, wie man sich selbst in der Lage macht, in bestimmten Kontexten nach meiner Erfahrung ein solcher Alptraum für die Instandhaltung zu werden.
Es hat viel mehr mit der Kopplung und der Richtung zu tun, in die Abhängigkeiten in Ihrem System fließen. Wenn Sie versuchen, eine abstrakte Rendering-Oberfläche, von der alles abhängt (weil sie sich selbst damit zeichnet), auf eine neue Ziel-API / -Hardware zu portieren, und feststellen, dass Sie ihr Design erheblich ändern müssen, damit sie dort effektiv funktioniert Dies ist eine sehr kostspielige Änderung, die das Ersetzen von Implementierungen von allem in Ihrem System erfordert, das weiß, wie man sich selbst zeichnet. Und das ist das praktischste Wartungsproblem, das mir begegnet, wenn ich weiß, wie man sich selbst zeichnet, wenn dies zu einer Schiffsladung von Abhängigkeiten führt, die in Richtung von Abstraktionen fließen, die zu schwierig sind, im Voraus richtig zu entwerfen.
Entwickler-Stolz
Ich erwähne diesen einen Punkt, weil er meiner Erfahrung nach oft das größte Hindernis ist, die Abhängigkeitsrichtung auf Dinge zu lenken, die einfacher zu gestalten sind. Für Entwickler ist es sehr einfach, hier ein wenig ehrgeizig zu werden und zu sagen: "Ich werde die plattformübergreifende Rendering-Abstraktion so gestalten, dass sie alle beherrschen. Ich werde lösen, was andere Entwickler monatelang für die Portierung aufwenden, und ich werde bekommen." es stimmt und es wird auf jeder einzelnen Plattform, die wir unterstützen, wie von Zauberhand funktionieren und auf jeder die neuesten Rendering-Techniken anwenden; ich habe es mir bereits in meinem Kopf vorgestellt. "In diesem Fall widersetzen sie sich der praktischen Lösung, das zu vermeiden, und kehren einfach die Richtung der Abhängigkeiten um und übersetzen die möglicherweise enorm kostspieligen und wiederkehrenden zentralen Entwurfsänderungen in einfach billige und lokal wiederkehrende Änderungen an der Implementierung. Es muss eine Art "weiße Fahne" -Instinkt in den Entwicklern geben, um aufzugeben, wenn es zu schwierig ist, etwas so abstrakt zu gestalten, und ihre gesamte Strategie zu überdenken, da sie sonst großen Kummer und Schmerzen ausgesetzt sind. Ich würde vorschlagen, solche Ambitionen und den Kampfgeist auf modernste Implementierungen zu übertragen, die einfacher zu entwerfen sind, als solche weltberühmenden Ambitionen auf die Ebene des Interface-Designs zu bringen.