Blog 5 min de lecture

Le moteur OGRE : une étude de cas sur les principes de la POO

Share this article
Le moteur OGRE : une étude de cas sur les principes de la POO

OGRE (Object-Oriented Graphics Rendering Engine) est un moteur de rendu 3D flexible, orienté scène, écrit en C++ et conçu pour rendre plus facile et plus intuitive la création d'applications exploitant des graphismes 3D accélérés matériellement. La bibliothèque de classes abstrait les détails d'utilisation des bibliothèques système sous-jacentes comme Direct3D et OpenGL et fournit une interface basée sur des objets du monde et d'autres classes de haut niveau.

Analysons-le avec CppDepend pour découvrir ses atouts en matière de conception.



Architecture d'OGRE3D

Le graphe de dépendances montre les relations entre les projets d'OGRE3D :



L'architecture est orientée plugins, ce qui est très utile pour étendre Ogre3D sans aucune modification du projet noyau « OgreMain ». Pour prendre en charge cette architecture, le projet OgreMain fournit les classes nécessaires pour communiquer avec les plugins.

Voyons comment le projet noyau communique avec les plugins en recherchant les classes d'OgreMain utilisées par le plugin RenderSystem_GL avec la requête suivante :

SELECT TYPES WHERE IsDirectlyUsedBy "RenderSystem_GL"


Le plugin utilise certaines classes utilitaires et entités du modèle, et redéfinit certaines classes abstraites pour s'intégrer dans l'écosystème Ogre3D.

Filtrons le résultat ci-dessous pour ne rechercher que les classes abstraites utilisées par le plugin RenderSystem_GL :

SELECT TYPES WHERE IsDirectlyUsedBy "RenderSystem_GL" AND IsAbstract



Le plugin peut redéfinir les scènes, les textures, le comportement de rendu, et bien plus. Ogre3D fournit une API intéressante pour personnaliser facilement le rendu, ce qui rend son adaptation à nos besoins très simple.

Héritage et polymorphisme

L'utilisation d'une approche orientée objet peut conduire à un usage excessif de l'héritage, dans le but de tirer pleinement parti du polymorphisme.

C'est particulièrement vrai pour OgreMain, qui représente le noyau du framework Ogre3D, où de nombreuses classes sont conçues pour être redéfinies par les projets plugins. SELECT TYPES FROM PROJECTS "OgreMain" WHERE NbBaseClass >0





Mais l'héritage multiple augmente la complexité, et il faut l'utiliser avec prudence. Recherchons les classes ayant de nombreuses classes de base.





Seules quelques classes dérivent de plus d’une classe.

Caractère abstrait

Pour une architecture orientée plugins, l'hôte doit posséder de nombreuses classes abstraites afin d'être plus flexible et extensible.

Recherchons les classes abstraites dans OgreMain : SELECT TYPES FROM PROJECTS "OgreMain" WHERE IsAbstract





Stratification des espaces de noms

CppDepend fournit un graphe DSM, et nous pouvons triangulariser cette matrice pour nous concentrer sur les bordures rouges, qui mettent en évidence les cycles de dépendances.



Un cycle de dépendances existe entre Ogre, Ogre::EmitterCommands et Ogre::OverlayElementCommands. Cette dépendance n'est pas nécessairement problématique, mais éviter ce genre de dépendance renforce le couplage faible ; cet intéressant article explique les avantages de la stratification.

Recherchons l'origine de la dépendance entre Ogre et les deux autres espaces de noms. SELECT TYPES WHERE IsDirectlyUsedBy "Ogre"





L'espace de noms Ogre utilise toutes les classes Cmd des autres espaces de noms, et toutes ces classes sont utilisées par Ogre::ParticleEmitter comme champs statiques ; ParticleEmitter les ajoute au dictionnaire CmdParam.

Il serait peut-être possible d'implémenter cela différemment pour éviter le cycle de dépendances, même si ce n'est pas un problème majeur.

Cohésion des types

Le principe de responsabilité unique stipule qu'une classe ne devrait pas avoir plus d'une raison de changer. Une telle classe est dite cohésive. Une valeur LCOM élevée signale généralement une classe peu cohésive. Il existe plusieurs métriques LCOM. LCOM prend ses valeurs dans l'intervalle [0-1]. LCOMHS (HS pour Henderson-Sellers) prend ses valeurs dans l'intervalle [0-2]. Notez que la métrique LCOMHS est souvent considérée comme plus efficace pour détecter les types non cohésifs. Une valeur LCOMHS supérieure à 1 devrait être considérée comme alarmante. SELECT TYPES WHERE LCOMHS > 0.95 AND NbFields > 10 AND NbMethods >10 AND !IsGlobal





Seules quelques classes sont considérées comme peu cohésives.

Design patterns utilisés

Singleton

Pour garantir qu'une classe n'a qu'une seule instance, la meilleure solution est d'utiliser le pattern singleton.

Recherchons les classes qui sont des singletons :

SELECT TYPES WHERE DeriveFrom "Ogre.Singleton"





Comme on peut le constater, presque toutes les classes de gestion (managers) sont des singletons — en général, un manager est un bon candidat pour être un singleton. Et nous pouvons rechercher les classes de gestion qui ne dérivent pas de Singleton SELECT TYPES WHERE !DeriveFrom "Ogre.Singleton" AND NameLike "Manager$"





Seuls quelques managers ne dérivent pas de Singleton, ce qui est normal car ces classes peuvent être instanciées plusieurs fois.

Factory

Le pattern factory est très utile pour abstraire la création d'objets ; il favorise un faible couplage et une forte cohésion, comme l'explique cet article.



Cependant, avoir une factory ne garantit pas qu'une instance soit créée par son intermédiaire, car la classe peut toujours être instanciée directement. Nous devons donc définir une règle pour nous assurer que la factory est bien utilisée pour l'instanciation.

Par exemple, pour la classe Entity, nous pouvons définir la règle suivante pour découvrir chaque classe qui l'instancie : SELECT METHODS WHERE DepthOfCreateA "Ogre.Entity" == 1





Comme on peut le voir, seule EntityFactory instancie la classe Entity.

Manager

Les classes de gestion (managers) donnent accès aux sous-systèmes et sont très utiles pour modulariser un projet. Ogre3D contient de nombreux managers, chacun représentant un sous-système différent.

SELECT TYPES WHERE NameLike "Manager$"





Façade

Une façade définit une interface de plus haut niveau qui rend le sous-système plus facile à utiliser, et nous pouvons détecter les façades dans votre projet en utilisant la métrique de couplage efférent (Efferent Coupling). Le couplage efférent d'un type particulier est le nombre de types dont il dépend directement. Les types où TypeCe > 50 dépendent de trop d'autres types. Ils sont complexes et ont plus d'une responsabilité. Ce sont de bons candidats pour une refactorisation.

Cependant, un TypeCe élevé peut être normal pour une façade.

Recherchons les classes avec un TypeCe élevé : SELECT TYPES ORDER BY TypeCe DESC





Toutes ces classes ne sont pas des façades, et il peut exister une explication valable au TypeCe élevé de chacune d'entre elles. Certaines pourraient peut-être être refactorisées, mais nous ne connaissons pas assez bien Ogre3D pour l'affirmer avec certitude.

La façade la plus utilisée est la classe Root ; voyons quels sous-systèmes elle utilise.



Comme on peut le constater, la classe Root utilise presque toutes les classes de gestion.

Nous pouvons aussi rechercher les managers qui ne sont pas accessibles via Root avec la requête CQL suivante :

SELECT TYPES WHERE !IsDirectlyUsedBy "Ogre.Root" AND NameLike "Manager$" AND !IsAbstract





Observateur

L'observateur est un pattern dans lequel un objet, appelé le sujet, maintient une liste de ses dépendants, appelés observateurs, et les notifie automatiquement de tout changement d'état, généralement en appelant l'une de leurs méthodes. Il est principalement utilisé pour implémenter des systèmes de gestion d'événements.

Ogre3D utilise des classes Listener pour implémenter le pattern observateur.

Recherchons les classes Listener dans le projet OgreMain. SELECT TYPES FROM PROJECTS "OgreMain" WHERE NameLike "Listener$"





Conclusion

Ogre3D est un framework très propre, bien conçu et bien documenté. On comprend facilement la finalité des design patterns qu'il utilise, et sa modularité peut vous aider à apprendre ses fonctionnalités plus rapidement.

Share this article