Ich weiß, dass es beim Erstellen von Anwendungen (nativ oder im Web) wie im Apple AppStore oder im Google Play App Store häufig vorkommt, eine Model-View-Controller-Architektur zu verwenden.
Ist es jedoch sinnvoll, Anwendungen auch mit der in Game-Engines üblichen Component-Entity-System-Architektur zu erstellen?
design-patterns
architecture
mvc
game-development
applications
Andrew De Andrade
quelle
quelle
Antworten:
Für mich absolut. Ich arbeite mit Visual FX und habe eine Vielzahl von Systemen in diesem Bereich untersucht, deren Architekturen (einschließlich CAD / CAM), hungrig nach SDKs und Papieren, die mir ein Gefühl für die Vor- und Nachteile der scheinbar unendlichen Architekturentscheidungen geben könnte gemacht werden, wobei selbst die subtilsten nicht immer einen subtilen Einfluss haben.
VFX ist Spielen insofern ziemlich ähnlich, als es ein zentrales Konzept einer "Szene" gibt, mit Ansichtsfenstern, die die gerenderten Ergebnisse anzeigen. Es gibt auch eine Menge zentraler Loop-Prozesse, die sich ständig um diese Szene drehen, und zwar in Animationskontexten, in denen möglicherweise Physik stattfindet, Partikel emittieren, Partikel spawnen, Netze animiert und gerendert werden, Bewegungsanimationen usw. und letztendlich, um sie zu rendern alles an den Benutzer am Ende.
Ein weiteres ähnliches Konzept für zumindest sehr komplexe Spiele-Engines war die Notwendigkeit eines "Designer" -Aspekts, bei dem Designer Szenen flexibel entwerfen konnten, einschließlich der Möglichkeit, eigene leichte Programme (Skripte und Knoten) zu erstellen.
Im Laufe der Jahre fand ich heraus, dass ECS am besten zu mir passte. Natürlich ist das nie vollständig von der Subjektivität getrennt, aber ich würde sagen, es schien die wenigsten Probleme zu geben. Es löste viel größere Probleme, mit denen wir immer zu kämpfen hatten, und gab uns nur ein paar neue kleinere zurück.
Traditionelles OOP
Traditionellere OOP-Ansätze können sehr effektiv sein, wenn Sie die Entwurfsanforderungen im Voraus genau kennen, aber nicht die Implementierungsanforderungen. Ob durch einen flacheren Ansatz mit mehreren Schnittstellen oder einen verschachtelten hierarchischen ABC-Ansatz, er zementiert tendenziell das Design und erschwert Änderungen, während die Implementierung einfacher und sicherer zu ändern ist. Es gibt immer ein Bedürfnis nach Instabilität in jedem Produkt, das über eine einzelne Version hinausgeht. Daher tendieren OOP-Ansätze dazu, die Stabilität (Schwierigkeit der Änderung und Fehlen von Gründen für die Änderung) in Richtung Designebene und Instabilität (Leichtigkeit der Änderung und Gründe für die Änderung) zu verzerren. auf die Implementierungsebene.
Im Gegensatz zu den sich ändernden Anforderungen für Benutzer müssen Design und Implementierung möglicherweise häufig geändert werden. Möglicherweise finden Sie etwas Seltsames wie ein starkes Bedürfnis der Benutzer nach der analogen Kreatur, die gleichzeitig sowohl Pflanze als auch Tier sein muss, was das gesamte von Ihnen erstellte konzeptionelle Modell vollständig ungültig macht. Normale objektorientierte Ansätze schützen Sie hier nicht und können solche unerwarteten, konzeptionellen Änderungen manchmal noch schwieriger machen. Wenn sehr leistungskritische Bereiche betroffen sind, multiplizieren sich die Gründe für das Design weiter.
Das Kombinieren mehrerer granularer Schnittstellen zu einer konformen Schnittstelle eines Objekts kann viel zur Stabilisierung des Clientcodes beitragen, nicht jedoch zur Stabilisierung der Subtypen, die manchmal die Anzahl der Clientabhängigkeiten in den Schatten stellen. Beispielsweise kann eine Schnittstelle nur von einem Teil Ihres Systems verwendet werden, aber mit tausend verschiedenen Subtypen, die diese Schnittstelle implementieren. In diesem Fall kann die Verwaltung der komplexen Subtypen (komplex, weil sie so viele unterschiedliche Schnittstellenverantwortlichkeiten zu erfüllen haben) eher zum Albtraum als zum Code werden, der sie über eine Schnittstelle verwendet. OOP tendiert dazu, Komplexität auf die Objektebene zu übertragen, während ECS sie auf die Clientebene ("Systeme") überträgt. Dies kann ideal sein, wenn es nur sehr wenige Systeme gibt, aber eine ganze Reihe konformer "Objekte" ("Entitäten").
Eine Klasse besitzt ihre Daten auch privat und kann so Invarianten alleine verwalten. Trotzdem gibt es "grobe" Invarianten, die eigentlich immer noch schwer zu pflegen sind, wenn Objekte miteinander interagieren. Damit ein komplexes System als Ganzes in einem gültigen Zustand ist, muss häufig ein komplexes Diagramm von Objekten berücksichtigt werden, auch wenn die einzelnen Invarianten ordnungsgemäß verwaltet werden. Traditionelle Ansätze im OOP-Stil können bei der Aufrechterhaltung granularer Invarianten hilfreich sein, können es jedoch tatsächlich schwierig machen, breite, grobe Invarianten aufrechtzuerhalten, wenn sich die Objekte auf winzige Facetten des Systems konzentrieren.
Das ist der Punkt, an dem solche ECS-Ansätze oder -Varianten zum Aufbau von Legoblöcken hilfreich sein können. Auch wenn Systeme gröber gestaltet sind als das übliche Objekt, ist es einfacher, solche groben Invarianten aus der Vogelperspektive des Systems zu betrachten. Viele Teeny-Objekt-Interaktionen werden zu einem großen System, das sich auf eine große Aufgabe konzentriert, anstatt auf kleine Teeny-Objekte, die sich auf kleine Teeny-Aufgaben mit einem Abhängigkeitsdiagramm konzentrieren, das einen Kilometer Papier abdecken würde.
Trotzdem musste ich mich außerhalb meines Fachgebiets in der Spieleindustrie umsehen, um mehr über ECS zu erfahren, obwohl ich immer eine datenorientierte Denkweise hatte. Lustigerweise habe ich mich auch fast selbstständig auf den Weg zu ECS gemacht, indem ich mich durchgearbeitet und versucht habe, bessere Designs zu finden. Ich habe es jedoch nicht bis zum Ende geschafft und ein sehr wichtiges Detail übersehen, nämlich die Formalisierung des Teils "Systeme" und das Zerquetschen von Komponenten bis hin zu Rohdaten.
Ich werde versuchen, herauszufinden, wie ich mich für ECS entschieden habe und wie ich alle Probleme mit früheren Entwurfsiterationen gelöst habe. Ich denke, das wird helfen, um genau zu verdeutlichen, warum die Antwort hier ein sehr starkes "Ja" sein könnte, dass ECS möglicherweise weit über die Gaming-Branche hinaus anwendbar ist.
Brute-Force-Architektur der 1980er Jahre
Die erste Architektur, an der ich in der VFX-Branche gearbeitet habe, hat ein langes Erbe hinter sich, das bereits ein Jahrzehnt zurückliegt, seit ich in das Unternehmen eingetreten bin. Es war eine rohe C-Codierung mit Brute-Force-Methode (keine Neigung zu C, wie ich C liebe, aber die Art und Weise, wie sie hier verwendet wurde, war wirklich roh). Ein miniaturisiertes und stark vereinfachtes Stück ähnelte den folgenden Abhängigkeiten:
Und dies ist ein enorm vereinfachtes Diagramm eines winzigen Teils des Systems. Jeder dieser Clients im Diagramm ("Rendern", "Physik", "Bewegung") würde ein "generisches" Objekt erhalten, durch das sie ein Typfeld wie folgt prüfen würden:
Natürlich mit deutlich hässlichem und komplexerem Code. Oft werden aus diesen Schalterfällen zusätzliche Funktionen aufgerufen, die den Schalter immer wieder und immer wieder rekursiv ausführen. Dieses Diagramm und Code aussehen könnten fast wie ECS-lite, aber es gab keine starke Einheit-Komponente Unterscheidung ( „ ist dieses Objekt eine Kamera?“, Nicht ‚dieses Objekt bietet Bewegung?‘), Und keine Formalisierung von ‚System‘ ( nur ein Bündel verschachtelter Funktionen, die überall verteilt sind und Verantwortlichkeiten vertauschen). In diesem Fall war fast alles kompliziert, jede Funktion war ein potenzielles Katastrophenrisiko.
Unsere Testprozedur hier musste oft Dinge wie Maschen, die von anderen Arten von Gegenständen getrennt sind, überprüfen, auch wenn beides identisch war, da die Brute-Force-Natur der hier vorgenommenen Codierung (oft begleitet von viel Kopieren und Einfügen) häufig vorkam Es ist sehr wahrscheinlich, dass ansonsten genau dieselbe Logik von einem Elementtyp zum nächsten fehlschlagen kann. Der Versuch, das System auf neue Arten von Gegenständen auszudehnen, war ziemlich aussichtslos, obwohl es ein stark geäußertes Bedürfnis der Benutzer gab, da es zu schwierig war, mit den vorhandenen Arten von Gegenständen so viel zu kämpfen.
Einige Profis:
Einige Nachteile:
1990er Jahre COM-Architektur
Der Großteil der VFX-Industrie verwendet diesen Architekturstil aus dem, was ich gesammelt habe, um Dokumente über ihre Designentscheidungen zu lesen und einen Blick auf ihre Softwareentwicklungskits zu werfen.
Auf der ABI-Ebene kann es sich nicht unbedingt um COM handeln (einige dieser Architekturen können nur Plugins enthalten, die mit demselben Compiler geschrieben wurden), sie weisen jedoch eine Reihe ähnlicher Merkmale bei Schnittstellenabfragen auf, die für Objekte durchgeführt werden, um festzustellen, welche Schnittstellen ihre Komponenten unterstützen.
Bei diesem Ansatz
transform
ähnelte die obige analoge Funktion dieser Form:Dies ist der Ansatz, auf den sich das neue Team dieser alten Codebasis festgelegt hat, um schließlich eine Umgestaltung zu erreichen. Und es war eine dramatische Verbesserung gegenüber dem Original in Bezug auf Flexibilität und Wartbarkeit, aber es gab noch einige Probleme, die ich im nächsten Abschnitt behandeln werde.
Einige Profis:
Einige Nachteile:
IMotion
immer den exakt gleichen Status und die exakt gleiche Implementierung für alle Funktionen. Um dies zu entschärfen, haben wir damit begonnen, Basisklassen und Hilfsfunktionen im gesamten System zu zentralisieren, um sicherzustellen, dass sie auf dieselbe Weise für dieselbe Schnittstelle redundant implementiert werden und möglicherweise mehrere Vererbungen hinter der Haube stattfinden chaotisch unter der Haube, obwohl der Client-Code es einfach hatte.QueryInterface
als mittlerer bis oberer Hotspot und gelegentlich sogar als Hotspot Nr. 1 angezeigt. Um dies zu entschärfen, müssten wir beispielsweise Teile des Codebasis-Cache mit einer Liste von Objekten rendern, von denen bekannt ist, dass sie diese unterstützenIRenderable
Dies erhöhte jedoch die Komplexität und die Wartungskosten erheblich. Dies war ebenfalls schwieriger zu messen, aber wir bemerkten einige deutliche Verlangsamungen im Vergleich zu der C-artigen Codierung, die wir zuvor durchgeführt hatten, als für jede einzelne Schnittstelle ein dynamischer Versand erforderlich war. Dinge wie Verzweigungsfehlvorhersagen und Optimierungsbarrieren sind außerhalb einer kleinen Codefacette schwer zu messen, aber die Benutzer bemerkten im Allgemeinen nur die Reaktionsfähigkeit der Benutzeroberfläche und solche Dinge, die sich verschlechterten, indem sie frühere und neuere Versionen der Software nebeneinander verglichen. Seite für Bereiche, in denen sich die algorithmische Komplexität nicht geändert hat, nur die Konstanten.Pragmatische Antwort: Zusammensetzung
Eines der Dinge, die wir zuvor bemerkt haben (oder zumindest ich), die Probleme verursacht haben, war, dass sie
IMotion
möglicherweise von 100 verschiedenen Klassen implementiert werden, aber mit genau der gleichen Implementierung und dem gleichen Status verbunden sind. Darüber hinaus würde es nur von wenigen Systemen wie Rendering, Keyframe-Bewegung und Physik verwendet.In einem solchen Fall besteht möglicherweise eine 3-zu-1-Beziehung zwischen den Systemen, die die Schnittstelle zur Schnittstelle verwenden, und eine 100-zu-1-Beziehung zwischen den Subtypen, die die Schnittstelle zur Schnittstelle implementieren.
Die Komplexität und Wartung würde dann drastisch auf die Implementierung und Wartung von 100 Subtypen anstatt von 3 Client-Systemen, die davon abhängen, verzerrt
IMotion
. Dies verlagerte alle unsere Wartungsschwierigkeiten auf die Wartung dieser 100 Untertypen, nicht der 3 Stellen, die die Schnittstelle verwenden. Aktualisierung von 3 Stellen im Code mit wenigen oder keinen "indirekten efferenten Kopplungen" (wie in Abhängigkeit davon, aber indirekt über eine Schnittstelle, keine direkte Abhängigkeit), keine große Sache: Aktualisierung von 100 Subtypstellen mit einer Bootsladung von "indirekten efferenten Kopplungen" , ziemlich große Sache *.Also musste ich hart pushen, aber ich schlug vor, dass wir versuchen, etwas pragmatischer zu werden und die Idee der "reinen Benutzeroberfläche" zu lockern. Es machte für mich keinen Sinn, so etwas wie
IMotion
komplett abstrakt und zustandslos zu machen, es sei denn, wir sahen einen Vorteil darin, dass es eine Vielzahl von Implementierungen gibt. In unserem FallIMotion
würde eine Vielzahl von Implementierungen tatsächlich zu einem ziemlichen Wartungs-Albtraum werden, da wir keine Vielfalt wollten . Stattdessen haben wir versucht, eine Single-Motion-Implementierung zu erstellen, die wirklich gut gegen sich ändernde Client-Anforderungen ist, und haben häufig die reine Schnittstellenidee umgangen, um jeden Implementierer zu zwingenIMotion
, die gleiche Implementierung und den gleichen Status zu verwenden, damit wir nicht ' t Ziele duplizieren.Interfaces wurden so mehr wie eine breite
Behaviors
Assoziation mit einer Entität.IMotion
würde einfach zu einerMotion
"Komponente" werden (ich habe die Art und Weise, wie wir "Komponente" definiert haben, von COM zu einer geändert, bei der die übliche Definition eines Teils, das eine "vollständige" Entität bildet, näher rückt).An Stelle von:
Wir haben es so weiterentwickelt:
Dies ist ein offensichtlicher Verstoß gegen das Prinzip der Abhängigkeitsumkehrung, um vom Abstrakten zum Konkreten zurückzukehren, aber für mich ist eine solche Abstraktionsebene nur dann nützlich, wenn wir in naher Zukunft zweifelsfrei einen echten Bedarf vorhersehen können und nicht für eine solche Flexibilität lächerliche "Was-wäre-wenn" -Szenarien auszuüben, die völlig unabhängig von der Benutzererfahrung sind (was wahrscheinlich ohnehin eine Designänderung erfordern würde).
Also entwickelten wir uns zu diesem Design.
QueryInterface
wurde mehr wieQueryBehavior
. Außerdem schien es sinnlos, hier Vererbung zu betreiben. Wir haben stattdessen Komposition verwendet. Aus Objekten wurde eine Sammlung von Komponenten, deren Verfügbarkeit zur Laufzeit abgefragt und injiziert werden konnte.Einige Profis:
Motion
Implementierung leichter bewältigt werden , z. B. und nicht auf hundert Subtypen verteilt.Einige Nachteile:
Ein Phänomen war, dass wir, da wir die Abstraktion dieser Verhaltenskomponenten verloren haben, mehr von ihnen hatten. Beispielsweise
IRenderable
würden wir anstelle einer abstrakten Komponente ein Objekt mit einem BetonMesh
oder einerPointSprites
Komponente verbinden. Das Wiedergabesystem würde wissen, wie esMesh
undPointSprites
Komponenten wiedergibt, und würde Entitäten finden, die solche Komponenten bereitstellen und diese zeichnen. Zu anderen Zeiten hatten wir verschiedene RenderablesSceneLabel
, von denen wir im Nachhinein festgestellt haben, dass sie erforderlich sind, und daher haben wirSceneLabel
in diesen Fällen ein an relevante Entitäten angehängt (möglicherweise zusätzlich zu einemMesh
). Die Rendering-System-Implementierung würde dann aktualisiert, um zu wissen, wie Entitäten gerendert werden, die diese bereitgestellt haben, und dies war eine ziemlich einfache Änderung.In diesem Fall kann eine Entität, die aus Komponenten besteht, auch als Komponente für eine andere Entität verwendet werden. Wir würden die Dinge auf diese Weise aufbauen, indem wir Legoblöcke anschließen.
ECS: Systeme und Rohdatenkomponenten
Das letzte System war so weit, dass ich es alleine geschafft habe, und wir haben es immer noch mit COM bastardiert. Es fühlte sich an, als wolle es ein Entity-Component-System werden, aber ich war zu der Zeit nicht damit vertraut. Ich habe mich nach Beispielen im COM-Stil umgesehen, die mein Fach gesättigt haben, als ich AAA-Game-Engines als Inspiration für die Architektur hätte betrachten sollen. Endlich habe ich damit angefangen.
Was mir fehlte, waren einige Schlüsselideen:
Schließlich verließ ich diese Firma und begann an einem ECS als Indy zu arbeiten (wobei ich immer noch daran arbeitete, während ich meine Ersparnisse abbaute), und es war bei weitem das am einfachsten zu verwaltende System.
Was mir beim ECS-Ansatz auffiel, war, dass es die Probleme löste, mit denen ich oben immer noch zu kämpfen hatte. Am wichtigsten für mich war, dass wir statt winziger kleiner Dörfer mit komplexen Interaktionen "Städte" in gesunder Größe verwalten. Es war nicht so schwer zu erhalten wie eine monolithische "Großstadt", zu groß in ihrer Bevölkerung, um effektiv verwaltet zu werden, aber nicht so chaotisch wie eine Welt voller winziger kleiner Dörfer, die miteinander interagieren und nur an die Handelsrouten denken zwischen ihnen bildete sich ein alptraumhaftes Diagramm. ECS hat die ganze Komplexität in Richtung sperriger "Systeme" destilliert, wie ein Rendering-System, eine "Stadt" von gesunder Größe, aber keine "übervölkerte Großstadt".
Komponenten, die zu Rohdaten wurden, fühlten sich für mich anfangs wirklich seltsam an, da dies sogar das grundlegende Prinzip des Versteckens von Informationen in OOP verletzt. Es war eine Art Herausforderung für einen der größten Werte, die mir an OOP besonders am Herzen lag, nämlich die Fähigkeit, Invarianten beizubehalten, die eine Kapselung und das Verstecken von Informationen erforderten. Aber es begann sich nicht weiter darum zu kümmern, wie schnell klar wurde, was mit nur einem Dutzend oder so breiten Systemen passierte, die diese Daten transformierten, anstatt dass eine solche Logik auf Hunderte bis Tausende von Subtypen verteilt wurde, die eine Kombination von Schnittstellen implementierten. Ich neige dazu, es als immer noch OOP-artig zu betrachten, mit der Ausnahme, dass die Systeme die Funktionalität und Implementierung bereitstellen, die auf die Daten zugreifen, die Komponenten die Daten bereitstellen und die Entitäten Komponenten bereitstellen.
Es wurde noch einfacher , die vom System verursachten Nebenwirkungen zu beurteilen, als es nur eine Handvoll sperriger Systeme gab, die die Daten in großen Schritten umwandelten. Das System wurde viel "flacher", meine Call-Stacks wurden für jeden Thread flacher als je zuvor. Ich könnte an das System auf dieser Ebene denken und nicht auf seltsame Überraschungen stoßen.
Ebenso wurden auch die leistungskritischen Bereiche hinsichtlich der Beseitigung dieser Abfragen vereinfacht. Da die Idee von "System" sehr formalisiert wurde, konnte ein System die Komponenten abonnieren, an denen es interessiert war, und nur eine zwischengespeicherte Liste von Entitäten erhalten, die diese Kriterien erfüllen. Diese Caching-Optimierung musste nicht von jedem Einzelnen durchgeführt werden, sondern wurde zentralisiert.
Einige Profis:
Einige Nachteile:
Ich würde also auf jeden Fall "Ja" sagen, wobei mein persönliches VFX-Beispiel ein starker Kandidat ist. Aber das ist immer noch ziemlich ähnlich wie beim Spielen.
Ich habe es nicht in entlegeneren Gegenden praktiziert, die völlig von den Bedenken der Game-Engines losgelöst sind (VFX ist ziemlich ähnlich), aber es scheint mir, dass weit mehr Gebiete gute Kandidaten für einen ECS-Ansatz sind. Vielleicht wäre sogar ein GUI-System für eines geeignet, aber ich verwende dort immer noch einen OOP-Ansatz (aber ohne tiefe Vererbung im Gegensatz zu Qt, zB).
Es ist ein weitgehend unerforschtes Gebiet, aber es scheint mir immer dann geeignet, wenn sich Ihre Entitäten aus einer reichen Kombination von "Merkmalen" zusammensetzen lassen (und genau, welche Kombination von Merkmalen sich je ändern wird) und wenn Sie eine Handvoll verallgemeinerter Merkmale haben Systeme, die Entitäten verarbeiten, die die erforderlichen Merkmale aufweisen.
In solchen Fällen wird es zu einer sehr praktischen Alternative zu jedem Szenario, in dem Sie möglicherweise versucht sind, so etwas wie Mehrfachvererbung oder eine Emulation des Konzepts (z. B. Mixins) zu verwenden, um nur Hunderte oder mehr Combos in einer Deep-Inheritance-Hierarchie oder Hunderte von Combos zu erstellen von Klassen in einer flachen Hierarchie, die eine bestimmte Kombination von Schnittstellen implementieren, bei denen jedoch nur wenige Systeme vorhanden sind (z. B. Dutzende).
In diesen Fällen fühlt sich die Komplexität der Codebasis proportionaler an als die Anzahl der Systeme anstelle der Anzahl der Typkombinationen, da jeder Typ nur noch eine Entität ist, die Komponenten zusammensetzt, die nichts weiter als Rohdaten sind. GUI-Systeme passen natürlich zu diesen Arten von Spezifikationen, bei denen Hunderte von möglichen Widget-Typen aus anderen Basistypen oder Schnittstellen kombiniert werden können, aber nur eine Handvoll von Systemen, um sie zu verarbeiten (Layout-System, Rendering-System usw.). Wenn ein GUI-System ECS verwendet, wäre es wahrscheinlich viel einfacher, über die Richtigkeit des Systems nachzudenken, wenn die gesamte Funktionalität von einer Handvoll dieser Systeme anstelle von Hunderten verschiedener Objekttypen mit vererbten Schnittstellen oder Basisklassen bereitgestellt wird. Wenn ein GUI-System ECS verwendet, haben Widgets keine Funktionalität, nur Daten. Nur eine Handvoll Systeme, die Widget-Entitäten verarbeiten, verfügen über Funktionen. Wie überschreibbare Ereignisse für ein Widget gehandhabt werden, ist mir unklar, aber aufgrund meiner bisher begrenzten Erfahrung habe ich keinen Fall gefunden, in dem diese Art von Logik nicht auf eine Weise zentral auf ein bestimmtes System übertragen werden konnte, die z Rückblickend ergab sich eine viel elegantere Lösung, die ich jemals erwarten würde.
Ich würde es gerne in mehr Bereichen einsetzen sehen, da es in meinen Bereichen ein Lebensretter war. Natürlich ist es ungeeignet, wenn Ihr Design nicht auf diese Weise zerfällt, von Einheiten, die Komponenten aggregieren, bis zu groben Systemen, die diese Komponenten verarbeiten. Wenn sie jedoch auf natürliche Weise zu dieser Art von Modell passen, ist es das Schönste, das mir bisher begegnet ist .
quelle
Die Component-Entity-System-Architektur für Game-Engines funktioniert für Spiele aufgrund der Art der Spielesoftware und ihrer einzigartigen Eigenschaften und Qualitätsanforderungen. Zum Beispiel bieten Entitäten ein einheitliches Mittel zum Adressieren und Arbeiten mit Dingen im Spiel, das sich in Zweck und Verwendung drastisch unterscheiden kann, jedoch vom System auf einheitliche Weise gerendert, aktualisiert oder serialisiert / deserialisiert werden muss. Durch die Einbindung eines Komponentenmodells in diese Architektur können sie eine einfache Kernstruktur beibehalten und bei geringer Codekopplung nach Bedarf weitere Features und Funktionen hinzufügen. Es gibt eine Reihe verschiedener Softwaresysteme, die von den Merkmalen dieses Entwurfs profitieren könnten, wie z. B. CAD-Anwendungen, A / V-Codecs,
TL; DR - Entwurfsmuster funktionieren nur dann gut, wenn die Problemdomäne für die Merkmale und Nachteile, die sie dem Entwurf auferlegen, ausreichend geeignet ist.
quelle
Wenn die Problemdomäne dafür gut geeignet ist, sicherlich.
Meine aktuelle Arbeit umfasst eine App, die abhängig von einer Reihe von Laufzeitfaktoren eine Vielzahl von Funktionen unterstützen muss. Die Verwendung komponentenbasierter Entitäten, um all diese Funktionen zu entkoppeln und Erweiterbarkeit und Testbarkeit für sich zu ermöglichen, war für uns idyllisch.
Bearbeiten: Meine Arbeit beinhaltet die Bereitstellung von Konnektivität für proprietäre Hardware (in C #). Abhängig vom Formfaktor der Hardware, der installierten Firmware, dem vom Client erworbenen Servicelevel usw. müssen wir dem Gerät unterschiedliche Funktionalitätsebenen bereitstellen. Sogar einige Funktionen mit derselben Schnittstelle sind je nach Version des Geräts unterschiedlich implementiert.
Bisherige Codebasen hatten hier sehr breite Schnittstellen, von denen viele nicht implementiert waren. Einige hatten viele dünne Schnittstellen, die dann statisch in einer Klasse zusammengesetzt wurden. Einige benutzten einfach String -> String-Wörterbücher, um es zu modellieren. (Wir haben viele Abteilungen, die alle glauben, dass sie es besser können)
Diese haben alle ihre Mängel. Breite Schnittstellen sind eineinhalb Schmerz, um effektiv zu verspotten / zu testen. Das Hinzufügen neuer Funktionen bedeutet das Ändern der öffentlichen Schnittstelle (und aller vorhandenen Implementierungen). Viele dünne Interfaces führten zu sehr hässlichem Code-Verbrauch, aber da wir am Ende herumgereicht haben, litten die Tests immer noch darunter. Außerdem haben die Thin-Interfaces ihre Abhängigkeiten nicht gut gemanagt. String-Wörterbücher weisen die üblichen Parsing- und Existenzprobleme sowie Performance-, Lesbarkeits- und Wartbarkeitslücken auf.
Was wir jetzt verwenden, ist eine sehr schlanke Entität, deren Komponenten basierend auf Laufzeitinformationen ermittelt und zusammengestellt werden. Abhängigkeiten werden deklarativ ausgeführt und vom Kernkomponenten-Framework automatisch aufgelöst. Die Komponenten selbst können isoliert getestet werden, da sie direkt mit ihren Abhängigkeiten arbeiten und Probleme mit fehlenden Abhängigkeiten frühzeitig erkannt werden - und zwar an einem Ort, anstatt die Abhängigkeit zum ersten Mal zu verwenden. Neue (oder Test-) Komponenten können eingefügt werden, und kein vorhandener Code ist davon betroffen. Verbraucher fragen die Entität nach einer Schnittstelle zu der Komponente, sodass wir uns mit den verschiedenen Implementierungen (und wie die Implementierungen Laufzeitdaten zugeordnet werden) relativ frei beschäftigen können.
Für eine Situation wie diese, in der die Zusammensetzung des Objekts und seiner Schnittstellen eine (sehr unterschiedliche) Teilmenge gemeinsamer Komponenten enthalten kann, funktioniert dies sehr gut.
quelle