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):
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:
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 PointLight
ist 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.
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).
- Ja, es gibt OOP-Konstruktionsprinzipien, die teilweise im Widerspruch zu SOLID stehen
- 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 PhysicsSystem
oder RenderSystem
oder 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):
... dazu, wo nur die Systeme Funktionalität haben (die blauen Komponenten sind jetzt nur Daten, und jetzt ist dies ein Kopplungsdiagramm):
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.