Verwenden Einzelhandelsspiele "Inversion of Control" und "Dependency Injection"?

30

Viele der fleißigeren Softwareentwickler, die ich kenne, tendieren zur Inversion von Steuerung und Abhängigkeitsinjektion , um Verweise auf Objekte zu verarbeiten. Aus der Perspektive der Flash-Spiele kenne ich nicht alle Details der AAA-Studios. Werden diese also in der Spielewelt des Einzelhandels verwendet?

Iain
quelle
Ich denke, dass in dieser Welt Leistung vor allem wichtig ist, da mehr Benutzer von geringer Leistung betroffen sind als Programmierer von schlechtem Code.
Bart van Heukelom
Abhängigkeitsinjektion klingt wie ein komponentenbasiertes System, aber hätten Sie ein Beispiel dafür, wie man eine Umkehrung der Kontrolle aufheben würde?
ADB
Da der größte Teil dieses Threads falsch zu sein scheint, was IoC ist und was es löst, und das OP nicht wirklich fragt, werde ich hier zunächst auf zukünftige Besucher hinweisen
Boris Callens
Ja, das tun sie. Tatsächlich sprach Rare darüber, wie sie ihre Tests in Sea of ​​Thieves implementieren - youtube.com/watch?v=KmaGxprTUfI&t=1s . Leider hatten sie nicht viel zu sagen über Inversion of Control in Unreal Engine, aber ich habe ein Projekt geschrieben, das zeigt, wie es funktioniert: github.com/jimmyt1988/UE-Testing
Jimmyt1988

Antworten:

45

Sie nennen es "sorgfältige Softwareentwicklung", ich nenne es "schmerzhafte Überentwicklung". Das heißt nicht, dass die Umkehrung der Kontrolle schlecht ist - in der Tat, die grundlegende Definition ist gut -, aber die Verbreitung ganzer Rahmenbedingungen und Arbeitsmethoden, um all dies zu erreichen, ist ein wenig verrückt, insbesondere in Kombination mit der Art und Weise, wie die Menschen Mist bauen Perfekt gute und saubere Schnittstellen, um austauschbare Komponenten einbauen zu können, die Sie in 99% der Fälle niemals austauschen werden. Dies ist die Art von Dingen, die nur aus der Java-Unternehmensumgebung stammen können, und ich bin froh, dass sie anderswo nicht so gut Fuß fassen.

Oft wird argumentiert, dass Sie auch dann, wenn Sie keine Komponenten austauschen, diese isoliert mit Scheinobjekten und dergleichen testen möchten. Ich fürchte jedoch, ich werde niemals das Argument kaufen, dass es sich lohnt, ein Interface aufzublähen und zu komplizieren, um es besser testen zu können. Testen beweist nur eines - dass Ihre Tests funktionieren. Saubere und minimale Benutzeroberflächen beweisen, dass Ihr Code funktioniert.

Also die kurze Antwort: Ja, aber nicht so, wie Sie denken. Wenn Sie austauschbares Verhalten benötigen, übergeben Sie manchmal ein Objekt an einen Konstruktor, der einen Teil des Verhaltens des neuen Objekts festlegt. Das ist alles.

Kylotan
quelle
5
+1 - Ich stimme zu, dass die Java-Community eine sehr einfache Idee zu haben scheint.
Chris Howe
4
Die Idee ist nicht unbedingt, dass Sie verschiedene Implementierungen in der Produktion austauschen müssen, sondern dass Sie möglicherweise spezielle Implementierungen einbauen möchten, um das automatisierte Testen zu vereinfachen. In dieser Hinsicht kann DI / IoC sicherlich in Spielen nützlich sein, aber Sie möchten den angemessenen Umfang beibehalten. Sie sollten nicht mehr als ein paar einmalige Dienstauflösungen auf hohem Niveau benötigen - Sie möchten nicht Dienste für jedes kleine Objekt im Spiel auflösen, und schon gar nicht für jeden Frame oder ähnliches.
Mike Strobel
2
@Thomas Dufour: Das Konzept eines Tests kann nie etwas beweisen. Es ist nur ein Datenpunkt. Sie können einen Testlauf 100000 Mal bestehen lassen, ohne zu beweisen, dass er den Lauf 100001 bestehen wird. Der Beweis stammt aus der logischen Analyse des Gegenstands. Ich würde argumentieren, dass diese IoC-Container und dergleichen das Testen auf Kosten der Verschärfung der Korrektheit vereinfachen, und ich halte das für eine schlechte Sache.
Kylotan
13
@Kylotan das Konzept eines Tests versucht nie , etwas zu beweisen . Es wird versucht zu demonstrieren, dass das Verhalten mit bestimmten Eingaben wie erwartet ist. Je mehr Verhalten sich als korrekt herausstellt, desto größer ist Ihre Überzeugung, dass die Gesamtfunktion korrekt ist, jedoch niemals 100%. Die Aussage, dass eine minimale Schnittstelle etwas beweist, ist lächerlich.
Dash-Tom-Bang
5
@Alex Schearer: Ich fürchte, ich kann nie akzeptieren, dass die Verwendung eines formalisierten IoC-Containersystems und das Erstellen zusätzlicher Konstruktorargumente oder Factory-Klassen zur Erleichterung des Testens den Code in irgendeiner Weise besser faktorisiert und sicherlich nicht macht es lockerer gekoppelt. Tatsächlich wird die Verkapselung, ein Schlüsselaspekt von OOP, mit Füßen getreten. Anstatt sich auf TDD zu verlassen, um das Design voranzutreiben und zu hoffen, dass ein gutes Ergebnis erzielt wird, können die Leute zunächst einmal die SOLID-Prinzipien befolgen.
Kylotan
17

Strategiemuster , Zusammensetzung und Abhängigkeitsinjektion sind alle sehr eng miteinander verbunden.

Da das Strategiemuster eine Form der Abhängigkeitsinjektion ist, basieren Motoren wie beispielsweise Unity vollständig auf diesem Prinzip. Ihre Verwendung von Komponenten (Strategy Pattern) ist tief in ihre gesamte Engine eingebettet.

Neben der Wiederverwendung von Komponenten besteht einer der Hauptvorteile darin, die gefürchteten tiefen Klassenhierarchien zu vermeiden.

Hier ist ein Artikel von Mick West, der darüber spricht, wie er diese Art von System in die Tony Hawk- Reihe von Spielen von Neversoft eingeführt hat.

Entwickeln Sie Ihre Hierarchie

Bis in die letzten Jahre haben Spieleprogrammierer konsequent eine tiefe Klassenhierarchie verwendet, um Spieleinheiten darzustellen. Die Flut beginnt, von dieser Verwendung tiefer Hierarchien zu einer Vielzahl von Methoden überzugehen, die ein Spielobjekt als Aggregation von Komponenten zusammensetzen ...

David Young
quelle
2
+1, Evolve Your Hierarchy ist ein großartiger Link, den ich völlig vergessen habe. Die Scott Bilas Folien sind auch gut - der richtige Link für diese ist ( scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf ). Es gibt noch einen Satz verwandter Folien, aber ich habe vergessen, wo ...
Leander
Auch der Artikel in Games Gems 5 (glaube ich) von Bjarne Rene.
Chris Howe
13

Es scheint viel Verwirrung über das Inversion-of-Control-Muster (IoC) zu geben. Einige Leute haben es mit dem Strategiemuster oder einem Komponentenmodell gleichgesetzt, aber dieser Vergleich erfasst nicht wirklich, worum es bei IoC geht. Bei IoC geht es wirklich darum, wie eine Abhängigkeit hergestellt wird. Lassen Sie mich Ihnen ein Beispiel geben:

class Game {
    void Load() {
        this.Sprite.Load(); // loads resource for drawing later
    }
}

class Sprite {
    void Load() {
        FileReader reader = new FileReader("path/to/resource.gif");
        // load image from file
    }
}

Oben ist klar, dass Sprite.Loades eine Abhängigkeit von a gibt FileReader. Wenn Sie die Methode testen möchten, benötigen Sie Folgendes:

  1. Ein vorhandenes Dateisystem
  2. Eine Testdatei, die aus dem Dateisystem geladen werden soll
  3. Möglichkeit, allgemeine Dateisystemfehler auszulösen

Die ersten beiden sind offensichtlich, aber wenn Sie sicherstellen möchten, dass Ihre Fehlerbehandlung wie erwartet funktioniert, brauchen Sie auch wirklich # 3. In beiden Fällen haben Sie Ihre Tests möglicherweise erheblich verlangsamt, da sie jetzt auf Festplatte gespeichert werden müssen, und Sie haben wahrscheinlich Ihre Testumgebung komplizierter gemacht.

Ziel von IoC ist es, den Gebrauch von Verhalten von seiner Konstruktion zu entkoppeln. Beachten Sie, wie sich dies vom Strategiemuster unterscheidet. Mit dem Strategiemuster soll ein wiederverwendbarer Teil des Verhaltens eingekapselt werden, damit Sie es in Zukunft problemlos erweitern können. Es gibt nichts darüber zu sagen, wie Strategien aufgebaut sind.

Wenn wir die Sprite.Loadobige Methode umschreiben würden, hätten wir wahrscheinlich folgendes Ergebnis:

class Sprite {
    void Load(IReader reader) {
        // load image through reader
    }
}

Jetzt haben wir die Konstruktion des Lesegeräts von seiner Verwendung entkoppelt. Daher ist es möglich, während des Testens einen Testleser einzutauschen. Dies bedeutet, dass Ihre Testumgebung kein Dateisystem und keine Testdateien mehr benötigt und problemlos Fehlerereignisse simulieren kann.

Beachten Sie, dass ich beim Umschreiben zwei Dinge getan habe. Ich habe eine Schnittstelle erstellt, die ein IReadergewisses Verhalten abbildet - dh das Strategiemuster implementiert. Außerdem habe ich die Verantwortung für die Erstellung des richtigen Lesers in eine andere Klasse verlagert.

Vielleicht brauchen wir keinen neuen Musternamen, um das oben Gesagte zu beschreiben. Es erscheint mir als eine Mischung aus Strategie- und Factory-Mustern (für IoC-Container). Abgesehen davon bin ich mir nicht sicher, aus welchen Gründen die Leute gegen dieses Muster Einwände erheben, da klar ist, dass es ein echtes Problem löst, und mir ist sicherlich nicht klar, was dies mit Java zu tun hat.

Alex Schearer
quelle
2
Alex: Niemand hat etwas dagegen, dass bereits erstellte Objekte übergeben werden, um bei Bedarf benutzerdefinierte Funktionen bereitzustellen. Jeder tut es, genau wie in Ihrem Beispiel. Das Problem ist, dass die Leute ganze Frameworks verwenden, die nur dafür existieren, und jeden Aspekt der Funktionalität religiös codieren, um von dieser Funktionalität abhängig zu sein. Sie fügen zuerst IReader als Parameter zur Sprite-Konstruktion hinzu und benötigen dann spezielle SpriteWithFileReaderFactory-Objekte, um dies zu vereinfachen usw. Diese Einstellung stammt aus Java und Dingen wie den Spring-IoC-Containern usw.
Kylotan
2
Aber wir sprechen nicht über IoC-Container - zumindest sehe ich das nicht im Originalbeitrag. Wir sprechen nur über das Muster selbst. Wie ich am besten beurteilen kann, lautet Ihr Argument: "Da einige Tools in der Natur schlecht sind, sollten wir die zugrunde liegenden Konzepte nicht verwenden." Das kommt mir so vor, als würde ich das Baby mit dem Badewasser rauswerfen. Das richtige Gleichgewicht zwischen Einfachheit, Erledigung von Aufgaben und Wartbarkeit / Testbarkeit zu finden, ist ein schwieriges Problem, das auf Projektbasis wahrscheinlich am besten behandelt wird.
Alex Schearer
Ich vermute, dass viele Leute gegen IoC sind, weil es bedeutet:
Phtrivier
4

Ich würde sagen, dass es ein Werkzeug unter vielen ist und gelegentlich verwendet wird. Wie Tenpn bereits sagte, kann jede Methode, die vtables einführt (und im Allgemeinen jede zusätzliche Indirektion), einen Leistungsverlust nach sich ziehen. Dies ist jedoch etwas, worüber wir uns nur bei Code auf niedriger Ebene Gedanken machen sollten. Für allgemeinen Strukturcode ist dies kein wirkliches Problem, und IoC kann positive Vorteile haben. Alles, um Abhängigkeiten zwischen Klassen zu verringern und Ihren Code flexibler zu gestalten.

Chris Howe
quelle
2
Um genau zu sein, würde ich mir Sorgen machen, dass jeder Code, der viele Male in einem Frame aufgerufen wird, eine Strafe für vtable erhält. Dies kann niedrig sein oder nicht.
Am
4

Ich muss Kylotan in dieser Frage zustimmen. "Dependency Injection" ist eine hässliche Java-Lösung für einen hässlichen Java-konzeptuellen Fehler, und der einzige Grund, warum sich jemand damit in anderen Sprachen befasst, ist, dass Java für viele Menschen eine Muttersprache wird, obwohl dies eigentlich nicht der Fall sein sollte.

Andererseits ist die Umkehrung der Kontrolle eine nützliche Idee, die es schon seit langer Zeit gibt und die sehr hilfreich ist, wenn sie richtig gemacht wird. (Nicht die Java / Dependency Injection-Methode.) In der Tat tun Sie dies wahrscheinlich die ganze Zeit, wenn Sie in einer vernünftigen Programmiersprache arbeiten. Als ich zum ersten Mal über dieses ganze "IOC" -Ding las, über das alle schwärmten, war ich völlig unterfordert. Inversion of Control ist nichts anderes als die Übergabe eines Funktionszeigers (oder Methodenzeigers) an eine Routine oder ein Objekt, um deren Verhalten anzupassen.

Diese Idee gibt es schon seit den 1950er Jahren. Es ist in der C - Standardbibliothek (qsort den Sinn kommt) , und es ist ganz über dem Platz in Delphi und .NET (Event - Handler / Teilnehmer). Es ermöglicht alten Code, neuen Code aufzurufen, ohne den alten Code neu kompilieren zu müssen, und wird in Game-Engines ständig verwendet.

Mason Wheeler
quelle
2
Ich war auch der „so das ist , was die Leute über angeregt werden?“ Wenn ich über DI lese, ist die Tatsache jedoch, dass die meisten Spieleprogrammierer es ertragen könnten, etwas darüber zu lernen und darüber, wie es auf die von uns geleistete Arbeit zutreffen würde.
Dash-Tom-Bang
2

Nach meiner Erfahrung nicht. Was schade ist, da Spiele-Code sehr anpassungsfähig sein muss und diese Ansätze definitiv helfen würden.

Es muss jedoch gesagt werden, dass beide Methoden in C ++ virtuelle Tabellen einführen können, bei denen es zuvor keine gab, was einen entsprechenden Leistungseinbruch mit sich bringt.

Tenpn
quelle
1

Ich bin kein professioneller Spieleentwickler und habe nur einmal versucht, IoC in C ++ zu implementieren. Das ist also nur Spekulation.

Ich vermute jedoch, dass Spieleentwickler gegenüber IoC misstrauisch sind, da dies Folgendes bedeutet:

1 / viele Schnittstellen und viele kleine Klassen entwerfen

2 / Fast jeder Funktionsaufruf ist in letzter Zeit gebunden

Nun mag es ein Zufall sein, aber beide neigen dazu, etwas komplizierter und / oder problematischer für die Leistung in C ++ (was im Spiel weit verbreitet ist, nicht wahr?) Zu sein als in Java, wo IoC weit verbreitet wurde (wahrscheinlich) weil es relativ einfach war, Menschen zu helfen, vernünftigere Objekthierarchien zu entwerfen, und weil einige glaubten, es könne das Schreiben einer Anwendung in Java in das Schreiben einer Anwendung in XML umwandeln, aber das ist eine andere Debatte: P)

Ich bin an den Kommentaren interessiert, wenn das überhaupt keinen Sinn ergibt;)

trivier
quelle