Blog 6 min de lecture

Les principes GRASP à travers le moteur 3D Irrlicht

Share this article
Les principes GRASP à travers le moteur 3D Irrlicht

Un design pattern est une solution générale et réutilisable à un problème récurrent dans un contexte donné de la conception logicielle. Les patterns sont des bonnes pratiques formalisées que le programmeur peut utiliser pour résoudre des problèmes courants lors de la conception d'une application ou d'un système.

Les patterns du « Gang of Four » sont peut-être les plus populaires. Cependant, il existe des principes de conception de base moins connus des développeurs : les General Responsibility Assignment Software Principles, abrégés GRASP.

Voici la définition de Wikipédia :

Les General responsibility assignment software patterns (ou principes), abrégés GRASP, consistent en des lignes directrices pour assigner les responsabilités aux classes et aux objets dans la conception orientée objet. Les différents patterns et principes utilisés dans GRASP sont : contrôleur, créateur, indirection, expert en information, forte cohésion, faible couplage, polymorphisme, variations protégées et fabrication pure. Tous ces patterns répondent à un problème logiciel, et ces problèmes sont communs à presque tous les projets de développement logiciel. Ces techniques n'ont pas été inventées pour créer de nouvelles façons de travailler, mais pour mieux documenter et standardiser des principes de programmation anciens et éprouvés de la conception orientée objet.

Irrlicht est une bibliothèque de moteur 3D qui utilise de nombreux principes GRASP ; découvrons les bénéfices de l'utilisation de ce genre de patterns.

Créateur

La création d'objets est l'une des activités les plus courantes d'un système orienté objet. Déterminer quelle classe est responsable de la création des objets est un aspect fondamental de la relation entre les objets de classes données. Prenons la classe de skin GUI comme exemple et découvrons où elle est créée dans la bibliothèque Irrlicht. Pour cela, exécutons cette requête CQLinq :

SELECT METHODS WHERE DepthOfCreateA “irr.gui.CGUISkin” == 1

CGUIEnvironment est la seule classe qui crée les instances de CGUISkin, et presque tous les éléments GUI sont créés par la classe CGUIEnvironment, à l'exception de CGUIButton.

SELECT METHODS WHERE DepthOfCreateA “irr.gui.CGUIButton” == 1

Comme on le voit, CGUIButton est créée à trois endroits différents ; il serait peut-être préférable de refactoriser le code et de déléguer la responsabilité de création à la classe CGUIEnvironment, comme pour toutes les autres classes GUI.

Contrôleur

Le pattern contrôleur assigne la responsabilité de traiter les événements système à une classe non-UI qui représente le système global ou un scénario de cas d'usage. Un objet contrôleur est un objet hors interface utilisateur chargé de recevoir ou de traiter un événement système.

Un contrôleur de cas d'usage devrait être utilisé pour traiter tous les événements système d'un cas d'usage et peut servir pour plus d'un cas d'usage (par exemple, pour les cas d'usage Créer un utilisateur et Supprimer un utilisateur, on peut avoir un seul UserControllerau lieu de deux contrôleurs de cas d'usage distincts).

Découvrons le contrôleur des éléments GUI, qui doit au minimum gérer le traitement des événements ; ce traitement est assuré par CGUIEnvironment::OnEvent.

Voyons quelles méthodes sont utilisées par OnEvent :

SELECT METHODS WHERE IsUsedBy “irr.gui.CGUIEnvironment.OnEvent(constSEvent&)”

Les événements déclenchés sont traités par une classe qui implémente la classe abstraite IEventReceiver. Explorons quelles classes l'implémentent.

Chaque élément GUI gère les événements qui le concernent.

Quelles sont les autres responsabilités de CGUIEnvironment ?

Comme nous l'avons vu précédemment, cette classe crée les classes concrètes et gère aussi le traitement des événements ; pour découvrir si elle a d'autres responsabilités, nous pouvons rechercher les méthodes utilisées par cette classe :

SELECT METHODS WHERE IsDirectlyUsedBy “irr.gui.CGUIEnvironment”

Cette classe utilise également certaines classes de l'espace de noms irr::io pour persister et charger des fichiers XML. Cette classe a peut-être de nombreuses responsabilités, ce qui peut affecter sa cohésion, mais cela reste tolérable car elle possède toutes les données nécessaires pour persister les données — elle suit le principe GRASP de l'« expert en information ».

Faible couplage

Un faible couplage est souhaitable, car une modification dans une partie d'une application nécessitera moins de changements dans l'ensemble de l'application. À long terme, cela pourrait faire gagner beaucoup de temps, d'efforts et d'argent lors de la modification d'une application ou de l'ajout de nouvelles fonctionnalités.

L'utilisation de classes abstraites peut aider à obtenir un faible couplage, et nous pouvons évaluer l'abstraction d'un module donné avec la métrique suivante :

A = Na / Nc

Où : * A = abstraction d'un module. Zéro correspond à un module totalement concret. Un correspond à un module totalement abstrait. * Na = nombre de classes abstraites dans le module. * Nc = nombre de classes concrètes dans le module.

L'abstraction d'Irrlicht est de 0,1245972, et il contient 125 classes abstraites. Dans l'espace de noms irr::gui, il y a 28 classes abstraites — pour chaque élément GUI, il existe une interface équivalente.

SELECT TYPES FROM NAMESPACES “irr.gui” WHERE IsAbstract

CppDepend fournit une DSM, et nous pouvons triangulariser cette matrice pour nous concentrer sur les bordures rouges qui mettent en évidence les classes fortement dépendantes, et pour détecter les modules.

Comme on le voit, toutes les classes abstraites sont regroupées, et elles pourraient être isolées dans un autre espace de noms, voire dans un autre projet. Pour mesurer le bénéfice de l'utilisation des classes abstraites afin d'obtenir un faible couplage, recherchons les classes qui utilisent la classe concrète CGUISkin.

SELECT METHODS WHERE IsDirectlyUsing “irr.gui.CGUISkin”

Une seule classe connaît directement cette classe : son créateur. Les autres classes concrètes sont utilisées à travers les classes abstraites, ce qui favorise un couplage lâche.

Qu'en est-il du couplage entre espaces de noms ?

Un cycle de dépendances existe entre certains espaces de noms ; avoir une telle dépendance n'est pas nécessairement problématique. Cependant, l'éviter favorise un couplage lâche.

Nous pouvons aussi découvrir comment les espaces de noms interagissent entre eux. Pour cela, voyons quelles classes et méthodes l'espace de noms « irr » utilise.

SELECT METHODS WHERE IsDirectlyUsedBy “irr”

Comme on le voit, presque toutes les interactions avec les autres espaces de noms passent par les classes abstraites, à l'exception d'irr::video::CVideoModeList et irr::scene::CMeshBuffer.

Découvrons l'origine de la dépendance envers irr::video::CVideoModeList. Pour cela, exécutons la requête CQLinq suivante :

SELECT METHODS OUT OF TYPES “irr.video.CVideoModeList” WHERE IsUsing “irr.video.CVideoModeList”

La classe irr::CIrrDeviceWin32 l'utilise parce qu'elle déclare un champ de type video::CVideoModeList au lieu de video::IVideoModeList. Une refactorisation pourrait peut-être être effectuée pour améliorer l'interaction entre les espaces de noms.

Forte cohésion

Le principe de responsabilité unique énonce 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. Le LCOM prend ses valeurs dans l'intervalle [0-1]. Le 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 doit être considérée comme alarmante.

SELECT TYPES WHERE LCOMHS > 0.95 AND NbFields > 10 AND NbMethods >10 AND!IsGlobal ORDER BY LCOMHS DESC

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

Conclusion

Irrlicht utilise les espaces de noms pour modulariser la base de code et les classes abstraites pour favoriser un faible couplage, ce qui le rend très facile à comprendre et à maintenir. C'est un bon exemple à suivre si vous voulez améliorer la qualité de votre conception.

Share this article