Les jeux de détail utilisent-ils «l'inversion de contrôle» et «l'injection de dépendance»?

30

Beaucoup des développeurs de logiciels les plus diligents que je connais passent à l' inversion du contrôle et à l' injection de dépendances pour gérer les références aux objets. Du point de vue des jeux Flash, je ne connais pas tous les tenants et aboutissants des studios AAA, donc: sont-ils utilisés dans le monde du jeu au détail?

Iain
la source
Je pense que dans ce monde, les performances sont avant tout importantes, car plus de utilisateurs seront touchés par de faibles performances que les programmeurs par un mauvais code.
Bart van Heukelom
L'injection de dépendances ressemble à un système basé sur des composants, mais auriez-vous un exemple de la façon dont une inversion de contrôle serait possible?
ADB
Comme la plupart de ce fil semble être mal informé sur ce qu'est l'IoC et ce qu'il résout et l'OP ne demande pas vraiment qu'en premier lieu je pointe ici pour les futurs visiteurs: sebaslab.com/ioc-container-unity-part-1
Boris Callens
Oui, ils le font. En fait, Rare a discuté de la façon dont ils mettent en œuvre leurs tests dans Sea of ​​Thieves - youtube.com/watch?v=KmaGxprTUfI&t=1s . Malheureusement, ils n'avaient pas grand-chose à dire sur l'inversion de contrôle dans Unreal Engine, cependant, j'ai écrit un projet qui montre comment cela fonctionne: github.com/jimmyt1988/UE-Testing
Jimmyt1988

Réponses:

45

Vous l'appelez «développement logiciel diligent», je l'appelle «ingénierie douloureuse». Cela ne veut pas dire que l'inversion du contrôle est mauvaise - en fait, la définition de base de celle-ci est bonne - mais la prolifération de cadres entiers et de méthodes de travail pour atteindre tout cela est un peu folle, surtout combinée avec la façon dont les gens saccagent des interfaces parfaitement bonnes et propres afin de pouvoir injecter des composants interchangeables que 99% du temps vous n'échangerez jamais. C'est le genre de chose qui ne peut provenir que de l'environnement d'entreprise Java et je suis heureux qu'il n'ait pas autant de place ailleurs.

Souvent, l'argument est que même si vous n'échangez pas de composants, vous voulez pouvoir les tester isolément avec des objets fantômes et similaires. Cependant, je crains de ne jamais accepter l'argument selon lequel cela vaut la peine de gonfler et de compliquer une interface afin de mieux la tester. Les tests prouvent une seule chose - que vos tests fonctionnent. D'un autre côté, des interfaces propres et minimales contribuent grandement à prouver que votre code fonctionne.

Donc, la réponse courte: oui, mais pas dans la façon dont vous pensez. Parfois, lorsque vous avez besoin d'un comportement interchangeable, vous transmettez un objet à un constructeur qui dicte une partie du comportement du nouvel objet. C'est à peu près ça.

Kylotan
la source
5
+1 - Je suis d'accord que la communauté Java semble avoir beaucoup trop compliqué ce qui semble être une idée très simple.
Chris Howe
4
L'idée n'est pas nécessairement que vous devrez échanger différentes implémentations en production, mais que vous souhaiterez peut-être injecter des implémentations spéciales pour faciliter les tests automatisés. À cet égard, DI / IoC peut certainement être utile dans les jeux, mais vous voulez le garder raisonnablement limité. Vous ne devriez pas avoir besoin de plus de quelques résolutions de service ponctuelles à un niveau élevé - vous ne voulez pas résoudre des services pour chaque petit objet du jeu, et certainement pas pour chaque trame ou quelque chose du genre.
Mike Strobel
2
@Thomas Dufour: le concept de test ne peut jamais prouver quoi que ce soit. Ce n'est qu'un point de données. Vous pouvez faire passer un test 100000 fois sans prouver qu'il passera au 100001. La preuve vient d'une analyse logique du sujet. Je dirais que ces conteneurs IoC et similaires facilitent les tests au détriment de la vérification de l'exactitude, et je pense que c'est une mauvaise chose.
Kylotan
13
@Kylotan le concept de test n'essaye jamais de prouver quoi que ce soit. Il essaie de démontrer que le comportement avec des entrées données est comme prévu. Plus il est démontré que le comportement est correct, plus vous croyez que la fonction globale est correcte, mais elle n'est jamais à 100%. L'affirmation qu'une interface minimale prouve tout est ridicule.
dash-tom-bang
5
@Alex Schearer: Je crains de ne jamais pouvoir accepter le fait d'avoir besoin d'utiliser un système de conteneurs IoC formalisé et de créer généralement des arguments de constructeur ou des classes d'usine supplémentaires pour faciliter les tests, ce qui rend le code mieux pris en compte, et certainement pas ce qui le rend plus lâche couplé. En fait, il piétine à peu près l'encapsulation, un aspect clé de la POO. Au lieu de compter sur TDD pour piloter leur conception et espérer qu'un bon résultat émerge, les gens pourraient simplement suivre les principes SOLIDES en premier lieu.
Kylotan
17

Le modèle de stratégie , la composition , l' injection de dépendance sont tous très étroitement liés.

Étant donné que le modèle de stratégie est une forme d'injection de dépendance, si vous regardez des moteurs comme Unity par exemple, ils sont complètement basés sur ce principe. Leur utilisation des composants (modèle de stratégie) est profondément ancrée dans l'ensemble de leur moteur.

Outre la réutilisation des composants, l'un des principaux avantages est d'éviter les hiérarchies de classes profondes redoutées.

Voici un article de Mick West qui explique comment il a introduit ce type de système dans la série de jeux Tony Hawk de Neversoft.

Faites évoluer votre hiérarchie

Jusqu'à des années assez récentes, les programmeurs de jeux ont toujours utilisé une hiérarchie de classes profonde pour représenter les entités de jeux. La marée commence à passer de cette utilisation de hiérarchies profondes à une variété de méthodes qui composent un objet d'entité de jeu comme une agrégation de composants ...

David Young
la source
2
+1, Evolve Your Hierarchy est un excellent lien que j'avais complètement oublié. Les diapositives de Scott Bilas sont également bonnes - le lien correct pour celles-ci est ( scottbilas.com/files/2002/gdc_san_jose/game_objects_slides.pdf ). Il y a une autre série de diapositives connexes, mais j'ai oublié où ...
leander
Aussi l'article dans Games Gems 5 (je pense) de Bjarne Rene.
Chris Howe
13

Il semble y avoir beaucoup de confusion au sujet du modèle d'inversion de contrôle (IoC). Un certain nombre de personnes l'ont assimilé au modèle de stratégie ou à un modèle de composant, mais ces comparaisons ne reflètent pas vraiment en quoi consiste l'IoC. L'IoC concerne vraiment la façon dont une dépendance est obtenue. Laisse moi te donner un exemple:

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
    }
}

Dans ce qui précède, il est clair que cela Sprite.Loaddépend de a FileReader. Pour tester la méthode, vous avez besoin des éléments suivants:

  1. Un système de fichiers en place
  2. Un fichier de test à charger depuis le système de fichiers
  3. Possibilité de déclencher des erreurs de système de fichiers courantes

Les deux premiers sont évidents, mais si vous voulez vous assurer que votre gestion des erreurs fonctionne comme prévu, vous avez vraiment besoin du n ° 3 également. Dans les deux cas, vous avez potentiellement ralenti considérablement vos tests car ils doivent maintenant aller sur le disque et vous avez probablement rendu votre environnement de test plus compliqué.

Le but de l'IoC est de dissocier l'utilisation du comportement de sa construction. Notez en quoi cela diffère du modèle de stratégie. Avec le modèle de stratégie, l'objectif est d'encapsuler un morceau de comportement réutilisable afin que vous puissiez facilement l'étendre à l'avenir; cela n'a rien à dire sur la façon dont les stratégies sont construites.

Si nous devions réécrire la Sprite.Loadméthode ci-dessus, nous finirions probablement par:

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

Maintenant, nous avons découplé la construction du lecteur de son utilisation. Par conséquent, il est possible d'échanger un lecteur de test pendant le test. Cela signifie que votre environnement de test n'a plus besoin d'un système de fichiers, de fichiers de test et peut facilement simuler des événements d'erreur.

Notez que j'ai fait deux choses dans ma réécriture. J'ai créé une interface IReaderqui a encapsulé un certain comportement - c'est-à-dire implémenté le modèle de stratégie. De plus, j'ai transféré la responsabilité de créer le bon lecteur à une autre classe.

Peut-être que nous n'avons pas besoin d'un nouveau nom de modèle pour décrire ce qui précède. Cela me semble être un mélange des modèles de stratégie et d'usine (pour les conteneurs IoC). Cela étant dit, je ne sais pas pour quelles raisons les gens s'opposent à ce modèle, car il est clair qu'il résout un problème réel, et, certainement, il n'est pas évident pour moi ce que cela a à voir avec Java.

Alex Schearer
la source
2
Alex: personne ne s'oppose à la transmission d'objets déjà créés pour générer des fonctionnalités personnalisées là où c'est nécessaire. Tout le monde le fait, comme dans votre exemple. Le problème est que les gens utilisent des frameworks entiers qui existent uniquement pour gérer ce genre de choses et codent religieusement chaque aspect de la fonctionnalité pour dépendre de cette fonctionnalité. Ils ajoutent d'abord IReader comme paramètre à la construction de Sprite, puis finissent par avoir besoin d'objets SpriteWithFileReaderFactory spéciaux pour faciliter cela, etc. Cette attitude provient de Java et de choses comme les conteneurs Spring IoC, etc.
Kylotan
2
Mais nous ne parlons pas de conteneurs IoC - au moins, je ne le vois pas dans le message d'origine. Nous parlons simplement du motif lui-même. Votre argument, du mieux que je puisse dire, est "Parce que certains outils dans la nature sont mauvais, nous ne devrions pas utiliser les concepts sous-jacents." Cela me semble jeter le bébé avec l'eau du bain. Trouver le bon équilibre entre la simplicité, faire avancer les choses et la maintenabilité / testabilité est un problème difficile qui sera probablement mieux traité projet par projet.
Alex Schearer
Je soupçonne que beaucoup de gens sont contre l'IoC car cela signifie:
phtrivier
4

Je dirais que c'est un outil parmi tant d'autres et qu'il est utilisé à l'occasion. Comme tenpn l'a dit, toute méthode qui introduit des vtables (et généralement toute indirection supplémentaire) peut avoir une pénalité de performance, mais c'est quelque chose dont nous ne devons nous soucier que pour le code de bas niveau. Pour le code structurel de haut niveau, ce n'est pas vraiment un problème, et l'IoC peut avoir des avantages positifs. Tout pour réduire les dépendances entre les classes et rendre votre code plus flexible.

Chris Howe
la source
2
Pour être précis, je m'inquiéterais des pénalités de table pour tout code appelé plusieurs fois par trame. Cela peut ou non être de bas niveau.
tenpn
4

Je dois être d'accord avec Kylotan sur celui-ci. "L'injection de dépendance" est une solution Java laide à un défaut conceptuel laid Java, et la seule raison pour laquelle quiconque la regarde dans d'autres langues est parce que Java est en train de devenir la première langue pour beaucoup de gens, alors qu'elle ne devrait vraiment pas.

L'inversion du contrôle, d'autre part, est une idée utile qui existe depuis longtemps et qui est très utile lorsqu'elle est bien faite. (Pas la méthode Java / Dependency Injection.) En fait, vous le faites probablement tout le temps si vous travaillez dans un langage de programmation sain. Quand j'ai lu pour la première fois toute cette histoire de "CIO", tout le monde bourdonnait, j'étais complètement déçu. L'inversion du contrôle n'est rien de plus que le passage d'un pointeur de fonction (ou pointeur de méthode) à une routine ou à un objet afin d'aider à personnaliser son comportement.

C'est une idée qui existe depuis les années 1950. C'est dans la bibliothèque standard C (qsort me vient à l'esprit) et c'est partout dans Delphi et .NET (gestionnaires d'événements / délégués). Il permet à l'ancien code d'appeler du nouveau code sans avoir besoin de recompiler l'ancien code, et il est utilisé tout le temps dans les moteurs de jeu.

Mason Wheeler
la source
2
J'étais aussi du « si c'est ce que les gens sont enthousiasmés? » quand j'ai lu à propos de DI, mais le fait est que la plupart des programmeurs de jeux pourraient se renseigner à ce sujet et comment cela s'appliquerait au travail que nous faisons.
dash-tom-bang
2

Pas d'après mon expérience. Ce qui est dommage, car le code des jeux doit être très adaptable, et ces approches seraient certainement utiles.

Cependant, il faut dire que les deux méthodes pourraient, en C ++, introduire des tables virtuelles là où il n'y en avait pas auparavant, apportant avec elles un résultat de performance approprié.

tenpn
la source
1

Je ne suis pas un développeur de jeux professionnel, et j'ai seulement essayé d'implémenter l'IoC en C ++, donc ce n'est que spéculation.

Cependant, je pense que les développeurs de jeux se méfieraient de l'IoC car cela signifie:

1 / Concevoir de nombreuses interfaces et de nombreuses petites classes

2 / ayant à peu près tous les appels de fonction liés récemment

Maintenant, cela pourrait être une coïncidence, mais les deux ont tendance à être un peu plus compliqués et / ou problématiques pour les performances en C ++ (qui est largement utilisé dans le jeu, n'est-ce pas?) Qu'en Java, où l'IoC s'est répandu (Probablement parce que c'était relativement facile à faire, aider les gens à concevoir des hiérarchies d'objets plus sûres, et parce que certains pensaient que cela pourrait transformer l'écriture d'une application en Java en écriture d'une application en XML, mais c'est un autre débat: P)

Je suis intéressé par les commentaires si cela n'a aucun sens;)

phtrivier
la source