Klassen von der Benutzeroberfläche entkoppeln

27

Was ist die beste Vorgehensweise beim Schreiben von Klassen, die möglicherweise etwas über die Benutzeroberfläche wissen müssen? Würde eine Klasse, die selbst zeichnen kann, nicht einige bewährte Methoden brechen, da dies von der Benutzeroberfläche (Konsole, GUI usw.) abhängt?

In vielen Programmierbüchern bin ich auf das Beispiel "Shape" gestoßen, das die Vererbung zeigt. Die Basisklassenform verfügt über eine draw () -Methode, mit der jede Form, z. B. ein Kreis und ein Quadrat, überschrieben wird. Dies ermöglicht Polymorphismus. Aber ist die draw () -Methode nicht sehr stark von der Benutzeroberfläche abhängig? Wenn wir diese Klasse beispielsweise für Win Forms schreiben, können wir sie nicht für eine Konsolen- oder Webanwendung wiederverwenden. Ist das richtig?

Der Grund für die Frage ist, dass ich immer wieder festgefahren bin und mich nicht sicher bin, wie ich Klassen verallgemeinern kann, damit sie am nützlichsten sind. Dies wirkt sich tatsächlich gegen mich aus und ich frage mich, ob ich es "zu sehr versuche".

Pete
quelle
Warum willst du dich entkoppeln? Weil Sie gehört haben, dass es das Richtige war, oder haben Sie andere Gründe?
SoylentGray
2
Die ganze "Klasse, die weiß, wie man sich selbst zeichnet" ist nur ein schreckliches, uraltes Beispiel, von dem ich mir wünschte, dass es verschwinden würde. Besonders auf dem Stapel des Spielprogrammierers =)
Patrick Hughes
2
@Chad Ich habe nicht wirklich viel außerhalb der Schule erlebt. Ich habe Bücher gelesen und lerne und lese sehr gerne Neues über Designmuster und Best Practices. Also ja, man kann sagen, ich habe gehört, dass Entkopplung gut ist, aber es macht auch Sinn. Ich möchte Code schreiben, den ich zum Beispiel für eine WinForms-Desktop-App verwenden kann, dann diesen Code verwenden und so viel wie möglich für eine Website oder sogar eine Silverlight-App wiederverwenden.
Pete
@Pete - Das ist eine gute Antwort.
SoylentGray
2
@Patrick: Um fair zu sein, wenn Sie eine ShapeKlasse schreiben , schreiben Sie wahrscheinlich den Grafikstapel selbst und nicht einen Client in den Grafikstapel.
Ken Bloom

Antworten:

13

Was ist die beste Vorgehensweise beim Schreiben von Klassen, die möglicherweise etwas über die Benutzeroberfläche wissen müssen? Würde eine Klasse, die selbst zeichnen kann, nicht einige bewährte Methoden brechen, da dies von der Benutzeroberfläche (Konsole, GUI usw.) abhängt?

Das hängt von der Klasse und dem Anwendungsfall ab. Ein visuelles Element, das weiß, wie man sich selbst zeichnet, ist nicht unbedingt ein Verstoß gegen das Prinzip der Einzelverantwortung.

In vielen Programmierbüchern bin ich auf das Beispiel "Shape" gestoßen, das die Vererbung zeigt. Die Basisklassenform verfügt über eine draw () -Methode, mit der jede Form, z. B. ein Kreis und ein Quadrat, überschrieben wird. Dies ermöglicht Polymorphismus. Aber ist die draw () -Methode nicht sehr stark von der Benutzeroberfläche abhängig?

Wieder nicht unbedingt. Wenn Sie eine Schnittstelle erstellen können (drawPoint, drawLine, set Color usw.), können Sie praktisch jeden Kontext zum Zeichnen von Objekten an die Form übergeben, z. B. im Konstruktor der Form. Dies würde es Formen ermöglichen, sich selbst auf einer Konsole oder einer gegebenen Zeichenfläche zu zeichnen.

Wenn wir diese Klasse beispielsweise für Win Forms schreiben, können wir sie nicht für eine Konsolen- oder Webanwendung wiederverwenden. Ist das richtig?

Nun, das stimmt. Wenn Sie ein UserControl (im Allgemeinen keine Klasse) für Windows Forms schreiben, können Sie es nicht mit einer Konsole verwenden. Das ist aber kein Problem. Warum sollte ein UserControl für Windows Forms für jede Art von Präsentation geeignet sein? Das UserControl sollte eines tun und es gut machen. Es ist per Definition an eine bestimmte Darstellungsform gebunden. Am Ende braucht der Benutzer etwas Konkretes und keine Abstraktion. Dies mag für Frameworks nur teilweise zutreffen, für Endbenutzeranwendungen jedoch.

Die Logik dahinter sollte jedoch entkoppelt sein, damit Sie sie wieder mit anderen Präsentationstechnologien verwenden können. Führen Sie bei Bedarf Schnittstellen ein, um die Orthogonalität für Ihre Anwendung aufrechtzuerhalten. Die allgemeine Regel lautet: Die konkreten Dinge sollten mit anderen konkreten Dingen austauschbar sein.

Der Grund für die Frage ist, dass ich immer wieder festgefahren bin und mich nicht sicher bin, wie ich Klassen verallgemeinern kann, damit sie am nützlichsten sind. Dies wirkt sich tatsächlich gegen mich aus und ich frage mich, ob ich es "zu sehr versuche".

Sie wissen, extreme Programmierer lieben ihre YAGNI- Einstellung. Versuchen Sie nicht, alles allgemein zu schreiben, und geben Sie sich nicht zu viel Mühe, um alles zu einem allgemeinen Zweck zu machen. Dies wird als Überentwicklung bezeichnet und führt schließlich zu völlig verschlungenem Code. Geben Sie jeder Komponente genau eine Aufgabe und vergewissern Sie sich, dass sie gut funktioniert. Fügen Sie bei Bedarf Abstraktionen ein, bei denen Sie Änderungen erwarten (z. B. Schnittstelle für den Zeichnungskontext, wie oben angegeben).

Im Allgemeinen sollten Sie beim Schreiben von Geschäftsanwendungen immer versuchen, Dinge zu entkoppeln. MVC und MVVM eignen sich hervorragend, um die Logik von der Präsentation zu entkoppeln, sodass Sie sie für eine Webpräsentation oder eine Konsolenanwendung wiederverwenden können. Denken Sie daran, dass am Ende einige Dinge konkret sein müssen. Ihre Benutzer können nicht mit einer Abstraktion arbeiten, sie brauchen etwas Konkretes. Abstraktionen sind nur eine Hilfe für Sie als Programmierer, um den Code erweiterbar und wartbar zu halten. Sie müssen sich überlegen, wo Sie Ihren Code brauchen, um flexibel zu sein. Schließlich müssen alle Abstraktionen etwas Konkretes hervorbringen.

Bearbeiten: Wenn Sie mehr über Architektur- und Designtechniken erfahren möchten, die Best Practices bieten, empfehlen wir Ihnen, die Antwort von @Catchops und SOLID- Praktiken auf Wikipedia zu lesen .

Außerdem empfehle ich für den Anfang immer das folgende Buch: Head First Design Patterns . Es wird Ihnen helfen, Abstraktionstechniken / OOP-Entwurfspraktiken besser zu verstehen als das GoF-Buch (was ausgezeichnet ist, es ist einfach nicht für Anfänger geeignet).

Falke
quelle
1
Gute Antwort, aber extreme Programmierer geben keine Abstraktionen ein, bei denen wir erwarten, dass sich die Dinge ändern. Wir stellen in Abstraktionen , wo die Dinge sind , zu ändern, um den Code zu vertrocknen.
Kevin Cline
1
@ Kevin Cline Das ist großartig, es sei denn, Sie entwerfen eine öffentlich zugängliche Bibliothek mit einer API, die dem vorherigen Verhalten und den Schnittstellen entsprechen muss.
Magnus Wolffelt
@Magnus - Ja, das Entwerfen einer Bibliothek in einigen Sprachen und das Planen der Abwärtskompatibilität mit Binärdateien ist schwierig. Man ist gezwungen, alle Arten von derzeit nicht benötigtem Code zu schreiben, um zukünftige Erweiterungen zu ermöglichen. Dies kann für Sprachen sinnvoll sein, die mit dem Metall kompiliert werden. Es ist albern für Sprachen, die zu einer virtuellen Maschine kompiliert werden.
Kevin Cline
19

Sie haben definitiv recht. Und nein, du versuchst es einfach nur gut :)

Lesen Sie mehr über das Prinzip der einmaligen Verantwortung

Ihre innere Arbeitsweise in der Klasse und die Art und Weise, wie diese Informationen dem Benutzer präsentiert werden sollen, sind zwei Verantwortlichkeiten.

Haben Sie keine Angst, den Unterricht zu entkoppeln. Selten ist das Problem zu viel Abstraktion und Entkopplung :)

Zwei sehr relevante Muster sind Model-View-Controller für Webanwendungen und Model View ViewModel für Silverlight / WPF.

Boris Yankov
quelle
1
+1 Sie können MVC auch für Nicht-Web-Anwendungen verwenden. Zumindest hilft es Ihnen dabei, in Begriffen zu denken, die die Verantwortlichkeiten klar machen.
Patrick Hughes
MVVM ist nichts für MVC. MVC ist hauptsächlich für zustandslose Anwendungen wie Web-Apps gedacht.
Boris Yankov
3
-1 Nach meiner Erfahrung ist eines der häufigsten Probleme die vorzeitige Verallgemeinerung und die Überentwicklung im Allgemeinen.
Dietbuddha
3
Sie müssen zuerst wissen, wie man etwas konstruiert, um es zu überarbeiten. Nach meiner Erfahrung wird Software von Leuten viel häufiger unterentwickelt.
Boris Yankov
Ich schätze zwar die Bemühungen, das Software-Design zu verbessern, aber das Ersetzen eines Aufrufs für eine Klasse durch einen Aufruf für eine Schnittstelle entkoppelt nichts semantisch. Überlegen Sie, was passiert, wenn Sie eine dynamisch eingegebene Sprache verwenden. Die oberflächliche (syntaktische) Entkopplung und die echte Entkopplung werden in den Ratschlägen im Abschnitt Programmierer zum Stapelaustausch viel zu stark betont.
Frank Hileman
7

Ich benutze MVVM häufig und meiner Meinung nach sollte eine Business-Objektklasse nie etwas über die Benutzeroberfläche wissen müssen. Sicher, sie müssen möglicherweise das SelectedItem, oder IsCheckedoder IsVisibleusw. kennen, aber diese Werte müssen nicht an eine bestimmte Benutzeroberfläche gebunden sein und können generische Eigenschaften der Klasse sein.

Wenn Sie etwas an der Schnittstelle im Code tun müssen, z. B. den Fokus einstellen, eine Animation ausführen, Hotkeys verwalten usw., sollte der Code Teil des Code-Behind der Benutzeroberfläche sein und nicht Ihre Geschäftslogikklassen.

Also würde ich sagen, hören Sie nicht auf, zu versuchen, Ihre Benutzeroberfläche und Ihre Klassen zu trennen. Je entkoppelter sie sind, desto einfacher können sie gewartet und getestet werden.

Rachel
quelle
5

Es gibt eine Reihe von bewährten Designmustern, die im Laufe der Jahre entwickelt wurden, um genau das zu erreichen, wovon Sie sprechen. Andere Antworten auf Ihre Frage beziehen sich auf das Prinzip der einheitlichen Verantwortlichkeit - das absolut gültig ist - und auf das, was Ihre Frage anzutreiben scheint. Dieser Grundsatz besagt einfach, dass eine Klasse eine Sache GUT tun muss. Mit anderen Worten, die Kohäsion erhöhen und die Kopplung verringern - das ist es, was gutes objektorientiertes Design ausmacht - macht eine Klasse eine Sache gut und hat nicht viele Abhängigkeiten von anderen.

Nun ... Sie haben Recht, wenn Sie einen Kreis auf einem iPhone zeichnen möchten, ist dies ein Unterschied zum Zeichnen eines Kreises auf einem PC, auf dem Windows ausgeführt wird. Sie MÜSSEN (in diesem Fall) eine konkrete Klasse haben, die einen Brunnen auf dem iPhone zeichnet, und eine andere, die einen Brunnen auf einem PC zeichnet. Dies ist der Grundbestandteil der Vererbung, den all diese Formenbeispiele auflösen. Sie können es einfach nicht mit Vererbung allein tun.

Hier kommen Schnittstellen ins Spiel - wie es in dem Buch der Viererbande heißt (MUSS GELESEN WERDEN) -. Ziehen Sie die Implementierung immer der Vererbung vor. Verwenden Sie mit anderen Worten Schnittstellen, um eine Architektur zusammenzustellen, die verschiedene Funktionen auf vielfältige Weise ausführen kann, ohne sich auf fest codierte Abhängigkeiten zu verlassen.

Ich habe Hinweise auf die SOLID- Prinzipien gesehen. Das sind großartig. Das "S" ist das Prinzip der Einzelverantwortung. ABER das 'D' steht für Dependency Inversion. Hier kann die Inversion des Kontrollmusters (Dependency Injection) verwendet werden. Es ist sehr leistungsfähig und kann verwendet werden, um die Frage zu beantworten, wie ein System aufgebaut werden kann, das einen Kreis sowohl für ein iPhone als auch für einen PC zeichnen kann.

Mit diesen Konstrukten kann eine Architektur erstellt werden, die allgemeine Geschäftsregeln und Datenzugriff enthält, jedoch verschiedene Implementierungen von Benutzeroberflächen enthält. Es ist jedoch sehr hilfreich, tatsächlich in einem Team zu sein, das es implementiert hat, und es in Aktion zu sehen, um es wirklich zu verstehen.

Dies ist nur eine schnelle allgemeine Antwort auf eine Frage, die eine genauere Antwort verdient. Ich ermutige Sie, sich eingehender mit diesen Mustern zu befassen. Etwas konkretere Implementierungen dieser Muster sind unter den bekannten Namen MVC und MVVM zu finden.

Viel Glück!

Catchops
quelle
Ich liebe es, wenn jemand etwas abstimmt, ohne einen Kommentar zu machen, warum (natürlich Sarkasmus). Die Prinzipien, die ich dargelegt habe, sind wahr - würde der Downvoter bitte aufstehen und sagen, warum.
Catchops
2

Was ist die beste Vorgehensweise beim Schreiben von Klassen, die möglicherweise etwas über die Benutzeroberfläche wissen müssen?

Würde eine Klasse, die selbst zeichnen kann, nicht einige bewährte Methoden brechen, da dies von der Benutzeroberfläche (Konsole, GUI usw.) abhängt?

In diesem Fall können Sie weiterhin MVC / MVVM verwenden und verschiedene UI-Implementierungen über die gemeinsame Schnittstelle einspeisen:

public interface IAgnosticChartDrawing
{
   public void Draw(ChartProperties chartProperties);
   event EventHandler ChartPanned;
}

public class GuiChartDrawer : UserControl, IAgnosticChartDrawing
{
    public void Draw(ChartProperties chartProperties)
    {
        //GDI, GTK or something else...
    }

    //Implement event based on mouse actions
}

public class ConsoleChartDrawer : IAgnosticChartDrawing
{
    public void Draw(ChartProperties chartProperties)
    {
        //'Draw' using characters and symbols...
    }

    //Implement event based on keyboard actions
}

IAgnosticChartDrawing guiView = new GuiChartDrawer();
IAgnosticChartDrawing conView = new ConsoleChartDrawer();

Model model = new FinancialModel();

SampleController controllerGUI = new SampleController(model, guiView);
SampleController controllerConsole = new SampleController(model, conView);

Auf diese Weise können Sie Ihre Controller- und Model-Logik wiederverwenden und gleichzeitig neue GUI-Typen hinzufügen.

Den
quelle
2

Dafür gibt es verschiedene Muster: MVP, MVC, MVVM usw.

Ein schöner Artikel von Martin Fowler (großer Name) zum Lesen ist GUI Architectures: http://www.martinfowler.com/eaaDev/uiArchs.html

MVP wurde noch nicht erwähnt, aber es ist definitiv zu erwähnen: Schauen Sie es sich an.

Es ist das von den Entwicklern von Google Web Toolkit vorgeschlagene Muster, es ist wirklich ordentlich.

Hier finden Sie echten Code, echte Beispiele und Gründe, warum dieser Ansatz nützlich ist:

http://code.google.com/webtoolkit/articles/mvp-architecture.html

http://code.google.com/webtoolkit/articles/mvp-architecture-2.html

Einer der Vorteile dieses oder eines ähnlichen Ansatzes, der hier nicht genug betont wurde, ist die Testbarkeit! In vielen Fällen würde ich sagen, das ist der Hauptvorteil!

Andrea Zilio
quelle
1
+1 für die Links. Ich habe einen ähnlichen Artikel über MVVM auf MSDN gelesen und diese Google-Artikel sind viel besser, wenn auch nach einem etwas anderen Muster.
Pete
1

Dies ist einer der Orte , an denen OOP nicht einen guten Job bei Abstraktion zu tun. Der OOP-Polymorphismus verwendet den dynamischen Versand über eine einzelne Variable ('this'). Wenn der Polymorphismus auf Shape basiert, können Sie ihn nicht polymorph auf dem Renderer (Konsole, GUI usw.) verteilen.

Stellen Sie sich ein Programmiersystem vor, das zwei oder mehr Variablen verarbeiten kann:

poly_draw(Shape s, Renderer r)

Angenommen, das System bietet Ihnen die Möglichkeit, poly_draw für verschiedene Kombinationen von Shape-Typen und Renderer-Typen auszudrücken. Es wäre dann einfach, eine Klassifizierung von Formen und Renderern zu finden, oder? Die Typprüfung würde Ihnen irgendwie helfen, herauszufinden, ob es Form- und Rendererkombinationen gibt, deren Implementierung Sie möglicherweise verpasst haben.

Die meisten OOP-Sprachen unterstützen nichts wie das oben Genannte (einige tun es, aber sie sind kein Mainstream). Ich würde vorschlagen, dass Sie sich das Besuchermuster ansehen, um dieses Problem zu umgehen.

Ritesh
quelle
1

... hängt die Methode draw () nicht stark von der Benutzeroberfläche ab? Wenn wir diese Klasse beispielsweise für Win Forms schreiben, können wir sie nicht für eine Konsolen- oder Webanwendung wiederverwenden. Ist das richtig?

Oben klingt für mich richtig. Nach meinem Verständnis bedeutet dies eine relativ enge Kopplung zwischen Controller und View in Bezug auf das MVC-Entwurfsmuster. Dies bedeutet auch, dass man zum Umschalten zwischen Desktop-Konsole und Web-App sowohl Controller als auch View als Paar entsprechend umschalten muss - das einzige Modell bleibt unverändert.

... Ich stecke immer wieder fest und bin gespannt, wie ich Klassen verallgemeinern kann, damit sie am nützlichsten sind.

Nun, meine derzeitige Auffassung ist, dass diese View-Controller-Kupplung, von der wir sprechen, in Ordnung ist und vor allem im modernen Design eher im Trend liegt .

Vor ein oder zwei Jahren fühlte ich mich auch unsicher. Ich habe meine Meinung geändert, nachdem ich im Sun-Forum über Muster und OO-Design diskutiert hatte.

Wenn Sie interessiert sind, probieren Sie dieses Forum aus - es wurde jetzt auf Oracle migriert ( Link ). Wenn Sie dort ankommen , versuchen Sie, Saish anzupingen - damals erwiesen sich seine Erklärungen zu diesen heiklen Dingen als äußerst hilfreich für mich. Ich kann allerdings nicht sagen, ob er noch dabei ist - ich selbst war schon eine ganze Weile nicht mehr dort

Mücke
quelle
1

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.

Bildbeschreibung hier eingeben

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.

Bildbeschreibung hier eingeben

  • 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.

Drachen Energie
quelle
1
Ich kann die Idee, "die Abhängigkeiten von den schwer zu gestaltenden Dingen umzukehren", scheinbar nicht begreifen. Reden Sie nur von Vererbung? Mit dem Beispiel von PSP / OpenGL meinst du, anstatt ein zu machen OpenGLBillboard, würdest du ein machen OpenGLRenderer, das weiß, wie man irgendeine Art zeichnet IBillBoard? Aber würde es das tun, indem es Logik an delegiert IBillBoard, oder würde es riesige Schalter oder IBillBoardTypen mit Bedingungen haben? Das ist, was ich schwer zu begreifen finde, denn das scheint überhaupt nicht wartbar zu sein!
Steve Chamaillard
@SteveChamaillard Der Unterschied besteht beispielsweise darin, dass PSP (eingeschränkte Hardware) Volume-Primitive mit alter Bildschirmtransparenz und einer anderen Reihenfolge der Renderauswertung verarbeiten muss: digitalrune.github.io/DigitalRune-Documentation/media/… . Wenn Sie eine Zentrale haben, RendererPspdie die Abstraktionen Ihrer Spieleszene auf höchster Ebene kennt, kann sie all die Magie und Rückschritte ausführen, die erforderlich sind, um solche Dinge auf eine Weise zu rendern, die auf der PSP überzeugend aussieht ...
Dragon Energy
Hatten Sie solche Volumenprimitive, die sich selbst rendern wollten, so sind ihre Operationen entweder so umfangreich, dass sie sich selbst für das Rendern spezifizieren (was keinen Unterschied außer Redundanz und weiterer Kopplung macht), oder die Renderer-Abstraktion ist nicht wirklich dazu in der Lage Die Dinge auf der PSP so effektiv wie möglich zu machen (zumindest ohne Backflips, was nicht nötig wäre, wenn die Abhängigkeiten vertauscht würden).
Dragon Energy
1
Ok, ich glaube, ich habe es jetzt verstanden. Grundsätzlich sagen Sie, dass solche Probleme (z. B. Hardware) so hoch sind, dass BillBoardes wirklich schwierig ist, Objekte auf niedriger Ebene, wie z. B. ein, von diesen abhängig zu machen? Während a IRendererbereits ein hohes Niveau aufweist und mit viel weniger Aufwand von diesen Bedenken abhängen könnte?
Steve Chamaillard
1
Angesichts der aktuellen Probleme beim Rendern auf unterschiedlichen Plattformen und der unterschiedlichen Hardwarefunktionen ist es zu schwierig, etwas zu entwerfen, bei dem ich der Chef bin, und ihm zu sagen, was zu tun ist. Es ist viel einfacher für mich zu sagen: "Hey, ich bin ein trübes / nebliges Ding mit grau-wässrigen Partikeln wie diesem und versuche, mich gut aussehen zu lassen, bitte fang meinen Hals nicht an, den ich in letzter Zeit nicht rasiert habe." und lassen Sie den Renderer herausfinden, wie er mich in Anbetracht der Einschränkungen, mit denen er arbeitet, so schön und in Echtzeit wie möglich rendern kann. Jeder Versuch, ihm zu sagen, was zu tun ist, wird mit ziemlicher Sicherheit zu einem kontraproduktiven Weg führen.
Dragon Energy