Gibt es OOP-Varianten, bei denen einige oder alle SOLID-Prinzipien dem Bereinigen von Code entgegenstehen?


26

Ich hatte vor kurzem eine Diskussion mit einem Freund über OOP in der Entwicklung von Videospielen.

Ich erklärte die Architektur eines meiner Spiele, das zur Überraschung meines Freundes viele kleine Klassen und mehrere Abstraktionsebenen enthielt. Ich argumentierte, dass dies das Ergebnis von mir war, dass ich mich darauf konzentrierte, alles einer einzigen Verantwortung zu unterwerfen und auch die Kopplung zwischen den Komponenten zu lösen.

Seine Sorge war, dass die große Anzahl von Klassen zu einem Alptraum für die Instandhaltung werden würde. Ich war der Meinung, dass dies genau das Gegenteil bewirken würde. Wir diskutierten dies für einige Jahrhunderte und waren uns schließlich einig, dass wir nicht einverstanden waren. Vielleicht gab es Fälle, in denen sich SOLID-Prinzipien und richtiger OOP nicht wirklich gut mischten.

Sogar der Wikipedia-Eintrag zu SOLID-Prinzipien besagt, dass es sich um Richtlinien handelt , die beim Schreiben von wartbarem Code helfen, und dass sie Teil einer Gesamtstrategie agiler und adaptiver Programmierung sind.

Meine Frage lautet also:

Gibt es Fälle in OOP, in denen sich einige oder alle SOLID-Prinzipien nicht dazu eignen, Code zu bereinigen?

Ich kann mir sofort vorstellen, dass das Liskov-Substitutionsprinzip möglicherweise einem anderen Aspekt der sicheren Vererbung widerspricht. Das heißt, wenn jemand ein anderes nützliches Muster entwickelt, das durch Vererbung implementiert wird, ist es durchaus möglich, dass der LSP in direktem Konflikt damit steht.

Gibt es noch andere Vielleicht funktionieren bestimmte Arten von Projekten oder bestimmte Zielplattformen besser mit einem weniger SOLID-Ansatz?

Bearbeiten:

Ich möchte nur spezifizieren, dass ich nicht frage, wie ich meinen Code verbessern kann;) Der einzige Grund, warum ich ein Projekt in dieser Frage erwähnte, war, einen kleinen Kontext anzugeben. Meine Frage bezieht sich auf OOP und Designprinzipien im Allgemeinen .

Wenn Sie neugierig auf mein Projekt sind, sehen Sie sich das an .

Bearbeiten 2:

Ich stellte mir vor, dass diese Frage auf drei Arten beantwortet werden könnte:

  1. Ja, es gibt OOP-Konstruktionsprinzipien, die teilweise im Widerspruch zu SOLID stehen
  2. Ja, es gibt OOP-Designprinzipien, die SOLID vollständig widersprechen
  3. Nein, SOLID ist die Knie der Biene und OOP wird für immer besser damit sein. Aber wie bei allem ist es kein Allheilmittel. Verantwortungsvoll trinken.

Die Optionen 1 und 2 hätten wahrscheinlich lange und interessante Antworten geliefert. Option 3 hingegen wäre eine kurze, uninteressante, aber insgesamt beruhigende Antwort.

Wir scheinen uns Option 3 anzunähern.


Wie definieren Sie "sauberen Code"?
GrandmasterB

Sauberer Code im Rahmen dieser Frage ist eine Codestruktur, die die Ziele von OOP und eine Reihe ausgewählter Entwurfsprinzipien erfüllt. Daher kenne ich mich mit sauberem Code in Bezug auf die von OOP + SOLID gesetzten Ziele aus. Ich frage mich, ob es noch andere Konstruktionsprinzipien gibt, die mit OOP funktionieren, mit SOLID jedoch ganz oder teilweise nicht kompatibel sind.
MetaFight

1
Ich mache mir mehr Sorgen um die Leistung deines Spiels als um irgendetwas anderes. Es sei denn, wir sprechen über textbasiertes Spiel.
Euphoric

1
Dieses Spiel ist im Wesentlichen ein Experiment. Ich probiere verschiedene Entwicklungsansätze aus und versuche auch herauszufinden, ob Enterprise-Code beim Schreiben eines Spiels funktionieren kann. Ich erwarte, dass Leistung zu einem Problem wird, aber es ist noch nicht geschehen. Wenn ja, dann werde ich als nächstes damit experimentieren, wie ich meinen Code in performanten Code umwandle. So viel lernen!
MetaFight

1
Mit der Gefahr einiger Abstimmungen möchte ich auf die "Warum" -Frage zurückkommen: OOP in der Spieleentwicklung . Aufgrund der Art der Spielentwicklung, „traditionelle“ OOP scheint der Mode für verschiedene Aromen von Unternehmen / Komponenten - Systeme, eine interessante Variante Herausfallen werden hier zum Beispiel. Das lässt mich eher denken, dass die Frage nicht "SOLID + OOP" sein sollte, sondern "OOP + Game Development". Wenn Sie sich ein Diagramm der Objektinteraktion für Spieleentwickler ansehen. Im Vergleich zu N-Tier-Anwendungen können die Unterschiede bedeuten, dass ein neues Paradigma gut ist.
J Trana

Antworten:


20

Gibt es Fälle in OOP, in denen sich einige oder alle SOLID-Prinzipien nicht dazu eignen, Code zu bereinigen?

Im Allgemeinen nicht. Die Geschichte hat gezeigt, dass die SOLID-Prinzipien in hohem Maße zu einer stärkeren Entkopplung beitragen, was sich wiederum in einer höheren Flexibilität des Codes und damit in der Fähigkeit niederschlägt, Änderungen Rechnung zu tragen und das Überlegen, Testen und Wiederverwenden des Codes zu erleichtern. Kurz gesagt, machen Sie Ihren Code sauberer.

Jetzt kann es Fälle geben, in denen die SOLID-Prinzipien mit DRY (wiederholen Sie sich nicht), KISS (halten Sie es einfach dumm) oder anderen Prinzipien eines guten OO-Designs kollidieren. Und natürlich können sie mit der Realität der Anforderungen, den Einschränkungen des Menschen, den Einschränkungen unserer Programmiersprachen und anderen Hindernissen kollidieren.

Kurz gesagt, SOLID-Prinzipien eignen sich immer zur Bereinigung von Code, aber in einigen Szenarien eignen sie sich weniger als widersprüchliche Alternativen. Sie sind immer gut, aber manchmal sind andere Dinge besser .


14
Ich mag, aber manchmal sind andere Dinge besser . Es hat ein "Animal Farm" Gefühl. ;)
FrustratedWithFormsDesigner

12
+1 Als ich mir gerade die SOLID-Prinzipien angesehen habe, sehe ich 5 "nice to haves". Aber keiner von ihnen übertrifft DRY, KISS und "machen Sie Ihre Absichten klar". Verwenden Sie SOLID, aber seien Sie vorsichtig und skeptisch.
user949300

Genau. Ich denke, die richtigen Prinzipien, die zu befolgen sind, hängen eindeutig von der Situation ab, aber abgesehen von den harten Leistungsanforderungen, die immer Vorrang vor allem anderen haben (dies ist insbesondere bei der Spieleentwicklung von Bedeutung), denke ich, dass DRY und KISS in der Regel beide wichtiger sind als SOLID. Je sauberer Sie den Code gestalten, desto besser. Wenn Sie also alle Prinzipien ohne Konflikte befolgen können, desto besser.
Ben Lee

Was ist mit der Trennung von Integration und effizientem Design von Aggregaten? Wenn die grundlegende "Sequenz" -Schnittstelle [zB IEnumerable] Methoden wie Countund asImmutableund Eigenschaften wie getAbilities[deren Rückgabe anzeigen würde, ob Dinge wie Count"effizient" sein werden] enthält, dann könnte man eine statische Methode haben, die mehrere Sequenzen nimmt und sie so aggregiert, dass sie verhält sich wie eine einzelne längere Sequenz. Wenn solche Fähigkeiten im grundlegenden Sequenztyp vorhanden sind, auch wenn sie nur mit Standardimplementierungen
verkettet sind

... und sie effizient implementieren, wenn sie mit Basisklassen verwendet werden, die dies können. Wenn eine Liste mit zehn Millionen Einträgen, deren Anzahl bekannt ist, und eine Liste mit fünf Einträgen, die nur sequentiell abrufen kann, zusammengefasst werden, sollte bei einer Anforderung für den 10.000.003. Eintrag die Anzahl aus der ersten Liste und dann drei aus der zweiten Liste gelesen werden . Kitchen-Sink-Schnittstellen verletzen möglicherweise den ISP, können jedoch die Leistung einiger Kompositions- / Aggregationsszenarien erheblich verbessern.
Supercat

13

Ich denke, ich habe eine ungewöhnliche Perspektive, da ich sowohl im Finanzbereich als auch im Spielebereich gearbeitet habe.

Viele der Programmierer, die ich in Spielen getroffen habe, waren schrecklich im Software Engineering - aber sie brauchten keine Praktiken wie SOLID. Traditionell packen sie ihr Spiel in eine Schachtel - dann sind sie fertig.

Ich habe festgestellt, dass Entwickler im Finanzbereich sehr schlampig und undiszipliniert sein können, da sie nicht die Leistung benötigen, die Sie in Spielen leisten.

Beide obigen Aussagen sind natürlich Über-Verallgemeinerungen, aber tatsächlich ist sauberer Code in beiden Fällen kritisch. Für die Optimierung ist klarer und leicht verständlicher Code unerlässlich. Aus Gründen der Wartungsfreundlichkeit möchten Sie dasselbe.

Das heißt nicht, dass SOLID nicht ohne Kritik ist. Sie heißen Prinzipien und sollen dennoch wie Richtlinien befolgt werden? Also, wann genau soll ich ihnen folgen? Wann sollte ich die Regeln brechen?

Sie könnten sich wahrscheinlich zwei Code-Teile ansehen und sagen, welcher am besten zu SOLID passt. Für sich allein gibt es jedoch keine objektive Möglichkeit, Code in SOLID umzuwandeln. Es geht definitiv um die Interpretation des Entwicklers.

Es ist keine Selbstverständlichkeit zu sagen, dass "eine große Anzahl von Klassen zu einem Alptraum der Instandhaltung führen kann". Wenn die SRP jedoch nicht richtig interpretiert wird, kann dieses Problem leicht auftreten.

Ich habe Code mit vielen Schichten von so ziemlich keiner Verantwortung gesehen. Klassen, die nichts anderes tun, als den Status von einer Klasse zur anderen zu übergeben. Das klassische Problem zu vieler Indirektionsebenen.

Wenn SRP missbraucht wird, kann dies zu einem Mangel an Kohäsion in Ihrem Code führen. Sie können dies feststellen, wenn Sie versuchen, eine Funktion hinzuzufügen. Wenn Sie immer mehrere Stellen gleichzeitig ändern müssen, fehlt Ihrem Code die Kohäsion.

Open-Closed ist nicht ohne Kritiker. Siehe beispielsweise Jon Skeets Überprüfung des Open Closed-Prinzips . Ich werde seine Argumente hier nicht wiedergeben.

Ihre Argumentation zu LSP scheint eher hypothetisch und ich verstehe nicht wirklich, was Sie mit einer anderen Form der sicheren Vererbung meinen?

ISP scheint SRP etwas zu duplizieren. Sicherlich müssen sie so interpretiert werden, wie Sie niemals vorhersagen können, was alle Clients Ihrer Schnittstelle tun werden. Die zusammenhängende Schnittstelle von heute ist der ISP-Verstoß von morgen. Die einzige unlogische Schlussfolgerung ist, dass nicht mehr als ein Mitglied pro Schnittstelle vorhanden ist.

DIP kann zu unbrauchbarem Code führen. Ich habe Klassen mit einer großen Anzahl von Parametern im Namen von DIP gesehen. Diese Klassen hätten normalerweise ihre zusammengesetzten Teile "neu" geschrieben, aber ich, der Name der Testfähigkeit, wir dürfen das neue Schlüsselwort nie wieder verwenden.

SOLID allein reicht nicht aus, um sauberen Code zu schreiben. Ich habe Robert C. Martins Buch " Clean Code: Ein Handbuch für agiles Software-Handwerk " gesehen. Das größte Problem dabei ist, dass es einfach ist, dieses Buch zu lesen und als Regeln zu interpretieren. Wenn Sie das tun, verpassen Sie den Punkt. Es enthält eine große Liste von Gerüchen, Heuristiken und Prinzipien - viel mehr als nur die fünf von SOLID. Alle diese "Prinzipien" sind keine wirklichen Prinzipien, sondern Richtlinien. Onkel Bob beschreibt sie selbst als "Cubby Holes" für Konzepte. Sie sind ein Balanceakt; Wenn Sie eine Richtlinie blind befolgen, treten Probleme auf.


1
Wenn Sie eine große Anzahl von Konstruktorparametern haben, deutet dies darauf hin, dass Sie die SRP verletzt haben. Ein DI-Container beseitigt jedoch ohnehin die mit einer großen Anzahl von Konstruktorparametern verbundenen Schmerzen, sodass ich Ihren Aussagen zu DIP nicht zustimme.
Stephen

Alle diese "Prinzipien" sind keine wirklichen Prinzipien, sondern Richtlinien. Sie sind ein Balanceakt; Wie ich in meinem Beitrag zu SRP bereits sagte, kann das Extreme selbst problematisch sein.
Dave Hillier

Gute Antwort, +1. Eine Sache möchte ich hinzufügen: Ich lese Skeets Rezension eher als Kritiker der Standardlehrbuchdefinition der OCP, nicht der Kernideen hinter der OCP. Nach meinem Verständnis ist die Idee der geschützten Variante das, was die OCP von Anfang an hätte sein sollen.
Doc Brown

5

Hier ist meine Meinung:

Obwohl SOLID-Prinzipien auf eine nicht redundante und flexible Codebasis abzielen, kann dies zu einem Kompromiss bei der Lesbarkeit und Wartung führen, wenn zu viele Klassen und Ebenen vorhanden sind.

Es geht vor allem um die Anzahl der Abstraktionen . Eine große Anzahl von Klassen ist möglicherweise in Ordnung, wenn sie auf wenigen Abstraktionen basieren. Da wir die Größe und die Einzelheiten Ihres Projekts nicht kennen, ist es schwer zu sagen, aber es ist etwas beunruhigend, dass Sie neben einer großen Anzahl von Klassen auch mehrere Ebenen erwähnen .

Ich denke, SOLID-Prinzipien stimmen nicht mit OOP überein, aber es ist immer noch möglich, nicht sauberen Code zu schreiben, der daran festhält.


3
+1 Per Definition muss ein hochflexibles System weniger wartbar sein als ein weniger flexibles. Flexibilität ist aufgrund der damit verbundenen zusätzlichen Komplexität mit Kosten verbunden.
Andy

@ Andy nicht unbedingt. Bei meinem derzeitigen Job sind viele der schlimmsten Teile des Codes chaotisch, weil sie einfach zusammengefügt werden, indem man "etwas Einfaches macht, es einfach hackt und es zum Laufen bringt, damit es nicht so komplex wird". Infolgedessen sind viele Teile des Systems zu 100% starr. Sie können sie überhaupt nicht ändern, ohne große Teile von ihnen neu zu schreiben. Es ist wirklich schwer zu pflegen. Die Wartbarkeit beruht auf einer hohen Kohäsion und einer geringen Kopplung, nicht darauf, wie flexibel das System ist.
Sara

3

In meinen frühen Entwicklungsjahren begann ich, ein Roguelike in C ++ zu schreiben. Da ich die gute objektorientierte Methodik anwenden wollte, die ich aus meiner Ausbildung gelernt hatte, sah ich die Spielelemente sofort als C ++ - Objekte. Potion, Swords, Food, Axe, JavelinsUsw. alle von einem grundlegenden Kern abgeleiteten ItemCode, Handhabung Namen, Symbol, Gewicht, solche Sachen.

Dann programmierte ich die Taschenlogik und sah mich einem Problem gegenüber. Ich kann Itemsin die Tasche stecken, aber wenn ich eine auswähle, woher weiß ich dann, ob es eine Potionoder eine ist Sword? Ich habe im Internet nachgeschlagen, wie man Gegenstände ablehnt. Ich habe die Compiler-Optionen gehackt, um Laufzeitinformationen zu aktivieren, damit ich weiß, was meine abstrahierten Itemwahren Typen sind, und die Logik für Typen umgeschaltet, um zu wissen, welche Operationen ich zulassen würde (z. B. das Trinken vermeiden Swordsund Potionsauf meine Füße setzen).

Es drehte den Code im Wesentlichen in eine große Kugel aus halb kopypastiertem Schlamm. Als das Projekt weiterging, begann ich zu verstehen, was der Designfehler war. Was passiert ist, ist, dass ich versucht habe, Klassen zu verwenden, um Werte darzustellen. Meine Klassenhierarchie war keine aussagekräftige Abstraktion. Was ich getan haben sollte macht Itemeine Reihe von Funktionen zu implementieren, wie Equip (), Apply ()und Throw (), und jede verhalten auf Werte basieren. Verwenden von Aufzählungen, um Ausrüstungsslots darzustellen. Verwenden von Aufzählungen, um verschiedene Waffentypen darzustellen. Verwenden Sie mehr Werte und weniger Klassifizierung, da meine Unterklassifizierung keinen anderen Zweck hatte, als diese endgültigen Werte zu füllen.

Da Sie der Objektmultiplikation unter einer Abstraktionsebene gegenüberstehen, denke ich, dass meine Einsicht hier wertvoll sein kann. Wenn Sie anscheinend zu viele Objekttypen haben, kann es sein, dass Sie die zu verwendenden Typen mit den zu verwendenden Werten verwechseln.


2
Das Problem bestand darin, Typen mit Wertklassen zu verwechseln (Anmerkung: Ich verwende das Wort class nicht, um auf den OOP / C ++ - Begriff class zu verweisen.) Es gibt mehr als eine Möglichkeit, einen Punkt in einer kartesischen Ebene darzustellen (rechteckige Koordinaten, polar) Koordinaten), aber sie sind alle Teil des gleichen Typs. Mainstream-OOP-Sprachen unterstützen die natürliche Klassifizierung von Werten jedoch nicht. Das nächste, was dem am nächsten kommt, ist die C / C ++ - Vereinigung, aber Sie müssen ein zusätzliches Feld hinzufügen, nur um zu wissen, was zum Teufel Sie dort einfügen.
Doval

4
Ihre Erfahrung kann als Anti-Beispiel verwendet werden. Indem Sie weder SOLID noch irgendeine Modellierungsmethode verwenden, haben Sie nicht verwaltbaren und nicht erweiterbaren Code erstellt.
Euphoric

1
@Euphoric Ich habe mich sehr bemüht. Ich hatte objektorientierte Methodik. Es gibt einen Unterschied zwischen einer Methodik und einer erfolgreichen Implementierung :)
Arthur Havlicek

2
Wie ist dies ein Beispiel für SOLID-Prinzipien, die im Widerspruch zu sauberem Code in OOP stehen? Es scheint eher ein Beispiel für ein falsches Design zu sein - dies ist orthogonal zu OOP!
Andres F.

1
Ihre Basis-ITEM-Klasse sollte eine Reihe von "canXXXX" -Booleschen Methoden enthalten, die standardmäßig FALSE sind. Sie könnten dann "canThrow ()" setzen, um in Ihrer Javelin-Klasse true zurückzugeben, und canEat (), um für Ihre Postion-Klasse true zurückzugeben.
James Anderson

3

Seine Sorge war, dass die große Anzahl von Klassen zu einem Alptraum für die Instandhaltung werden würde. Ich war der Meinung, dass dies genau das Gegenteil bewirken würde.

Ich bin absolut auf der Seite Ihres Freundes, aber es könnte eine Frage unserer Bereiche und der Art der Probleme und Entwürfe sein, die wir angehen, und insbesondere, welche Arten von Dingen in Zukunft wahrscheinlich Änderungen erfordern. Unterschiedliche Probleme, unterschiedliche Lösungen. Ich glaube nicht an richtig oder falsch, sondern nur an Programmierer, die versuchen, den bestmöglichen Weg zu finden, um ihre speziellen Designprobleme am besten zu lösen. Ich arbeite in VFX, was nicht allzu unähnlich zu Game Engines ist.

Aber das Problem, mit dem ich in einer SOLID-konformen Architektur zu kämpfen hatte (sie war COM-basiert), konnte grob auf "zu viele Klassen" oder "zu viele Funktionen" als "reduziert" werden Ihr Freund könnte beschreiben. Ich würde speziell sagen: "Zu viele Interaktionen, zu viele Orte, die sich möglicherweise schlecht verhalten könnten, zu viele Orte, die möglicherweise Nebenwirkungen hervorrufen könnten, zu viele Orte, die möglicherweise geändert werden müssen, und zu viele Orte, die möglicherweise nicht das tun, was wir glauben, dass sie tun . "

Wir hatten eine Handvoll abstrakter (und reiner) Schnittstellen, die von einer Schiffsladung von Subtypen implementiert wurden (in diesem Diagramm im Zusammenhang mit den Vorteilen von ECS wurde der Kommentar unten links ignoriert):

Bildbeschreibung hier eingeben

Wo eine Bewegungsschnittstelle oder eine Szenenknotenschnittstelle von Hunderten von Untertypen implementiert werden kann: Lichter, Kameras, Netze, Physiklöser, Shader, Texturen, Knochen, primitive Formen, Kurven usw. usw. (und es gab oft mehrere Typen von jedem ). Und das ultimative Problem war wirklich, dass diese Designs nicht so stabil waren. Wir hatten wechselnde Anforderungen und manchmal mussten sich die Schnittstellen selbst ändern. Wenn Sie eine abstrakte Schnittstelle ändern möchten, die von 200 Subtypen implementiert wird, ist dies eine äußerst kostspielige Änderung. Wir begannen, dies zu mildern, indem wir abstrakte Basisklassen verwendeten, die die Kosten für solche Konstruktionsänderungen reduzierten, aber dennoch teuer waren.

Als Alternative habe ich mich mit der Entity-Component-Systemarchitektur befasst, die in der Spielebranche eher verbreitet ist. Das hat alles so verändert:

Bildbeschreibung hier eingeben

Und wow! Das war so ein Unterschied in Bezug auf die Wartbarkeit. Die Abhängigkeiten flossen nicht mehr zu Abstraktionen , sondern zu Daten (Komponenten). Und zumindest in meinem Fall waren die Daten trotz der sich ändernden Anforderungen viel stabiler und einfacher zu erstellen (obwohl sich das, was wir mit denselben Daten tun können, bei sich ändernden Anforderungen ständig ändert).

Auch weil die Entitäten in einem ECS Komposition anstelle von Vererbung verwenden, müssen sie eigentlich keine Funktionalität enthalten. Sie sind nur der analoge "Container der Komponenten". Das machte es so die analogical 200 Subtypen , die eine Bewegung umgesetzt Schnittstelle wiederum in 200 Entity - Instanzen (nicht getrennte Typen mit separatem Code) , die einfach eine Bewegung speichern Komponente (was nichts anderes ist als Daten mit Bewegung verbunden). A PointLightist keine separate Klasse / Subtyp mehr. Es ist überhaupt keine Klasse. Es ist eine Instanz einer Entität, die nur einige Komponenten (Daten) kombiniert, die sich auf die Position im Raum (Bewegung) und die spezifischen Eigenschaften von Punktlichtern beziehen. Die einzige Funktionalität, die mit ihnen verbunden ist, befindet sich in den Systemen wie demRenderSystem, der nach Lichtkomponenten in der Szene sucht, um zu bestimmen, wie die Szene gerendert werden soll.

Da sich die Anforderungen im Rahmen des ECS-Ansatzes änderten, mussten häufig nur ein oder zwei Systeme geändert werden, die mit diesen Daten arbeiten, oder es musste nur ein neues System an der Seite eingeführt oder eine neue Komponente eingeführt werden, wenn neue Daten benötigt wurden.

Zumindest für meine Domain, und ich bin mir fast sicher, dass dies nicht für alle gilt, hat dies die Dinge so viel einfacher gemacht, da die Abhängigkeiten in Richtung Stabilität flossen (Dinge, die sich überhaupt nicht oft ändern mussten). Dies war in der COM-Architektur nicht der Fall, als die Abhängigkeiten einheitlich zu Abstraktionen flossen. In meinem Fall ist es viel einfacher, herauszufinden, welche Daten für die Bewegung im Voraus erforderlich sind, als alle möglichen Dinge, die Sie damit tun können, was sich im Laufe der Monate oder Jahre häufig ein wenig ändert, wenn neue Anforderungen eingehen.

Bildbeschreibung hier eingeben

Gibt es Fälle in OOP, in denen sich einige oder alle SOLID-Prinzipien nicht dazu eignen, Code zu bereinigen?

Gut, sauberer Code kann ich nicht sagen, da manche Leute sauberen Code mit SOLID gleichsetzen, aber es gibt definitiv einige Fälle, in denen die Trennung von Daten von der Funktionalität wie beim ECS und die Umleitung von Abhängigkeiten von Abstraktionen zu Daten die Dinge auf jeden Fall viel einfacher machen können Ändern Sie aus offensichtlichen Kopplungsgründen, wenn die Daten viel stabiler sind als die Abstraktionen. Natürlich können Abhängigkeiten von Daten die Pflege von Invarianten erschweren, aber ECS tendiert dazu, dies durch die Systemorganisation auf ein Minimum zu reduzieren, wodurch die Anzahl der Systeme, die auf einen bestimmten Komponententyp zugreifen, minimiert wird.

Abhängigkeiten müssen nicht unbedingt in Richtung Abstraktionen fließen, wie DIP nahelegt. Abhängigkeiten sollten sich auf Dinge auswirken, bei denen es sehr unwahrscheinlich ist, dass sie künftig geändert werden müssen. Das kann in allen Fällen Abstraktionen sein oder nicht (es war sicherlich nicht in meinem Fall).

  1. Ja, es gibt OOP-Konstruktionsprinzipien, die teilweise im Widerspruch zu SOLID stehen
  2. Ja, es gibt OOP-Designprinzipien, die SOLID vollständig widersprechen.

Ich bin mir nicht sicher, ob ECS wirklich ein Geschmack von OOP ist. Einige Leute definieren es so, aber ich sehe es als sehr unterschiedlich in den Kopplungseigenschaften und der Trennung von Daten (Komponenten) von Funktionalität (Systemen) und der fehlenden Datenkapselung. Wenn es als eine Form von OOP betrachtet wird, würde ich denken, dass es sehr im Widerspruch zu SOLID steht (zumindest die strengsten Vorstellungen von SRP, Open / Closed, Liskov-Substitution und DIP). Ich hoffe jedoch, dass dies ein vernünftiges Beispiel für einen Fall und ein Gebiet ist, in dem die grundlegendsten Aspekte von SOLID, zumindest wenn die Leute sie allgemein in einem besser erkennbaren OOP-Kontext interpretieren würden, möglicherweise nicht so zutreffend sind.

Teeny Klassen

Ich erklärte die Architektur eines meiner Spiele, das zur Überraschung meines Freundes viele kleine Klassen und mehrere Abstraktionsebenen enthielt. Ich argumentierte, dass dies das Ergebnis von mir war, dass ich mich darauf konzentrierte, alles einer einzigen Verantwortung zu unterwerfen und auch die Kopplung zwischen den Komponenten zu lösen.

Das ECS hat meine Ansichten stark in Frage gestellt und verändert. Wie Sie dachte ich früher, dass die Idee der Wartbarkeit die einfachste Implementierung für Dinge ist, die möglich ist, was viele Dinge und darüber hinaus viele voneinander abhängige Dinge impliziert (auch wenn die Abhängigkeiten zwischen Abstraktionen bestehen). Es ist am sinnvollsten, wenn Sie nur auf eine Klasse oder Funktion zoomen, um die einfachste Implementierung zu sehen, und wenn wir keine sehen, überarbeiten Sie sie und zerlegen Sie sie möglicherweise sogar weiter. Es kann jedoch leicht vorkommen, dass Sie übersehen, was mit der Außenwelt passiert, da jedes Mal, wenn Sie etwas relativ Komplexes in zwei oder mehr Dinge aufteilen, diese zwei oder mehr Dinge in einigen Fällen unweigerlich miteinander interagieren müssen (siehe unten) Weise oder etwas außerhalb muss mit allen von ihnen interagieren.

Heutzutage finde ich, dass es einen Spagat zwischen der Einfachheit von etwas und wie vielen Dingen gibt und wie viel Interaktion erforderlich ist. Die Systeme in einem ECS sind in der Regel ziemlich umfangreich, da nicht-triviale Implementierungen die Daten wie PhysicsSystemoder RenderSystemoder verarbeiten GuiLayoutSystem. Die Tatsache, dass ein komplexes Produkt so wenige von ihnen benötigt, erleichtert es jedoch, einen Schritt zurückzutreten und über das Gesamtverhalten der gesamten Codebasis nachzudenken. Da ist etwas dran, was darauf hindeuten könnte, dass es keine schlechte Idee ist, sich auf die Seite weniger massigerer Klassen zu stellen (die immer noch eine wohl singuläre Verantwortung tragen), wenn dies bedeutet, dass weniger Klassen gepflegt und begründet werden müssen und weniger Interaktionen stattfinden das System.

Wechselwirkungen

Ich sage eher "Interaktionen" als "Kopplung" (obwohl das Reduzieren von Interaktionen das Reduzieren von beiden impliziert), da Sie Abstraktionen verwenden können, um zwei konkrete Objekte zu entkoppeln, aber sie immer noch miteinander sprechen. Sie könnten im Verlauf dieser indirekten Kommunikation immer noch Nebenwirkungen verursachen. Und oft stelle ich fest, dass meine Fähigkeit, über die Korrektheit eines Systems nachzudenken, eher mit diesen "Wechselwirkungen" als mit "Kopplung" zusammenhängt. Das Minimieren von Interaktionen erleichtert es mir, alles aus der Vogelperspektive zu betrachten. Das heißt, Dinge, die überhaupt nicht miteinander sprechen, und in diesem Sinne tendiert ECS auch dazu, "Interaktionen" wirklich zu minimieren und nicht nur auf das Nötigste zu koppeln (zumindest habe ich keine Ahnung).

Das könnte zumindest teilweise an mir und meinen persönlichen Schwächen liegen. Ich habe das größte Hindernis für mich gefunden, Systeme von enormer Größe zu erstellen, und immer noch selbstbewusst darüber nachzudenken, durch sie zu navigieren und das Gefühl zu haben, dass ich potenzielle gewünschte Änderungen auf vorhersehbare Weise überall vornehmen kann Nebenwirkungen. Es ist das größte Hindernis, das auftaucht, wenn ich von Zehntausenden von LOC zu Hunderttausenden von LOC zu Millionen von LOC wechsle, selbst für Code, den ich komplett selbst geschrieben habe. Wenn mich etwas vor allem zu einem Crawl verlangsamt, kann ich in diesem Sinne nicht mehr nachvollziehen, was in Bezug auf Anwendungsstatus, Daten und Nebenwirkungen vor sich geht. Es' Es ist nicht die Roboterzeit, die für eine Änderung erforderlich ist, die mich so sehr bremst, sondern die Unfähigkeit, die vollständigen Auswirkungen der Änderung zu verstehen, wenn das System über die Fähigkeit meines Verstandes hinauswächst, darüber nachzudenken. Und die Reduzierung der Interaktionen war für mich die effektivste Möglichkeit, das Produkt mit viel mehr Funktionen zu vergrößern, ohne dass ich persönlich von diesen Dingen überwältigt werde, da die Reduzierung der Interaktionen auf ein Minimum die Anzahl der möglichen Stellen ebenfalls verringert möglicherweise sogar den Anwendungszustand ändern und erhebliche Nebenwirkungen verursachen.

Es kann sich so etwas drehen (wo alles im Diagramm Funktionalität hat und offensichtlich ein reales Szenario die vielfache Anzahl von Objekten haben würde, und dies ist ein "Interaktions" -Diagramm, kein Kopplungsdiagramm, als Kopplung man hätte abstraktionen dazwischen):

Bildbeschreibung hier eingeben

... dazu, wo nur die Systeme Funktionalität haben (die blauen Komponenten sind jetzt nur Daten, und jetzt ist dies ein Kopplungsdiagramm):

Bildbeschreibung hier eingeben

Und es tauchen Überlegungen dazu auf und vielleicht eine Möglichkeit, einige dieser Vorteile in einen konformeren OOP-Kontext einzufügen, der besser mit SOLID kompatibel ist, aber ich habe die Entwürfe und Wörter noch nicht ganz gefunden, und ich finde es schwierig, da die Terminologie, die ich gewohnt war, alle direkt mit OOP zu tun zu haben. Ich versuche immer wieder, die Antworten der Leute durchzulesen und mein Bestes zu geben, um meine eigenen zu formulieren, aber es gibt einige interessante Dinge an der Natur eines ECS, auf die ich nicht perfekt eingehen konnte kann sogar auf Architekturen angewendet werden, die es nicht verwenden. Ich hoffe auch, dass diese Antwort nicht als ECS-Promotion herauskommt! Ich finde es einfach sehr interessant, da das Entwerfen eines ECS meine Gedanken drastisch verändert hat.


Ich mag die Unterscheidung zwischen Interaktionen und Kopplung. Obwohl Ihr erstes Interaktionsdiagramm verschleiert ist. Es ist ein planares Diagramm, daher müssen keine Pfeile gekreuzt werden.
D Drmmr

2

SOLID-Prinzipien sind gut, aber KISS und YAGNI haben bei Konflikten Vorrang. Der Zweck von SOLID besteht darin, die Komplexität zu verwalten. Wenn SOLID jedoch selbst angewendet wird, wird der Code komplexer, wodurch der Zweck zunichte gemacht wird. Wenn Sie beispielsweise ein kleines Programm mit nur wenigen Klassen haben, kann das Anwenden von DI, Schnittstellentrennung usw. die Gesamtkomplexität des Programms sehr gut erhöhen.

Das Open / Closed-Prinzip ist besonders umstritten. Grundsätzlich heißt es, dass Sie einer Klasse Features hinzufügen sollten, indem Sie eine Unterklasse oder eine separate Implementierung derselben Schnittstelle erstellen, die ursprüngliche Klasse jedoch unberührt lassen. Wenn Sie eine Bibliothek oder einen Dienst verwalten, die bzw. der von nicht koordinierten Clients verwendet wird, ist dies sinnvoll, da Sie das Verhalten, auf das sich der Client verlassen kann, nicht unterbrechen möchten. Wenn Sie jedoch eine Klasse ändern, die nur in Ihrer eigenen Anwendung verwendet wird, ist es oft einfacher und übersichtlicher, die Klasse einfach zu ändern.


In Ihrer Diskussion über OCP wird die Möglichkeit ignoriert, das Verhalten einer Klasse über Komposition zu erweitern (z. B. mithilfe eines Strategieobjekts, damit ein von der Klasse verwendeter Algorithmus geändert werden kann). Dies wird im Allgemeinen als bessere Methode zur Einhaltung des Prinzipals als Unterklassen angesehen .
Jules

1
Wenn KISS und YAGNI immer Priorität hatten, sollten Sie immer noch Einsen und Nullen eingeben. Assembler sind nur etwas anderes, was schief gehen kann. KISS und YAGNI weisen nicht auf zwei der vielen Kosten für Designarbeit hin. Diese Kosten sollten immer gegen den Nutzen jeglicher Konstruktionsarbeit abgewogen werden. SOLID hat Vorteile, die in einigen Fällen die Kosten ausgleichen. Es gibt keine einfache Möglichkeit, festzustellen, wann Sie die Grenze überschritten haben.
candied_orange

@CandiedOrange: Ich bin nicht der Meinung, dass Maschinencode einfacher ist als Code in einer höheren Sprache. Maschinencode enthält aufgrund der fehlenden Abstraktion eine Menge zufälliger Komplexität. Daher ist es definitiv nicht KISSs Entscheidung, eine typische Anwendung in Maschinencode zu schreiben.
JacquesB

@Jules: Ja, Komposition ist vorzuziehen, aber Sie müssen die ursprüngliche Klasse dennoch so entwerfen, dass Komposition zum Anpassen verwendet werden kann. Daher müssen Sie Annahmen darüber treffen, welche Aspekte in Zukunft angepasst werden müssen und welche Aspekte sind nicht anpassbar. In einigen Fällen ist dies erforderlich (z. B. wenn Sie eine Bibliothek für die Verteilung schreiben), dies ist jedoch mit Kosten verbunden. Daher ist es nicht immer optimal , SOLID zu folgen .
JacquesB

1
@JacquesB - wahr. Und OCP ist im Kern YAGNI gegenübergestellt. Wie auch immer Sie vorgehen, um dies zu erreichen, müssen Sie Erweiterungspunkte entwerfen, um eine spätere Verbesserung zu ermöglichen. Einen geeigneten Ausgleich zwischen den beiden Idealen zu finden, ist ... schwierig.
Jules

1

Durch meine Erfahrung:-

Große Klassen sind exponentiell schwieriger zu warten, wenn sie größer werden.

Kleine Klassen sind einfacher zu verwalten und die Schwierigkeit, eine Sammlung kleiner Klassen zu verwalten, steigt rechnerisch mit der Anzahl der Klassen.

Manchmal sind große Klassen unvermeidbar, ein großes Problem erfordert manchmal eine große Klasse, aber sie sind viel schwieriger zu lesen und zu verstehen. Für gut benannte kleinere Klassen kann nur der Name ausreichen, um zu verstehen, dass Sie nicht einmal den Code betrachten müssen, es sei denn, es liegt ein sehr spezielles Problem mit der Klasse vor.


0

Es fällt mir immer schwer, die Grenze zwischen dem „S“ des Festkörpers und dem (möglicherweise altmodischen) Bedürfnis zu ziehen, einem Objekt all das Wissen über sich selbst zu geben.

Vor 15 Jahren arbeitete ich mit Deplhi-Entwicklern zusammen, die ihren heiligen Gral hatten, um Klassen zu haben, die alles enthielten. Für eine Hypothek könnten Sie also Versicherungen und Zahlungen sowie Einkommen und Darlehen und Steuerinformationen hinzufügen und die zu zahlende Steuer berechnen und abziehen, und sie könnte sich selbst serialisieren, auf der Festplatte verbleiben usw. usw.

Und jetzt habe ich dasselbe Objekt in viele, viele kleinere Klassen unterteilt, die den Vorteil haben, dass sie viel einfacher und besser Unit-getestet werden können, aber keinen guten Überblick über das gesamte Geschäftsmodell geben.

Und ja, ich weiß, dass Sie Ihre Objekte nicht an die Datenbank binden möchten, aber Sie können ein Repository in der großen Hypothekenklasse hinzufügen, um das zu lösen.

Außerdem ist es nicht immer einfach, gute Namen für Klassen zu finden, die nur eine winzige Aufgabe haben.

Ich bin mir also nicht sicher, ob das "S" immer eine gute Idee ist.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.