informa
5 MIN READ
Blogs

Irrlicht:The art of using GRASP patterns

When we search for design pattern articles, we found essentially documentation concerning “Gang of Four” patterns. But when I discovered GRASP patterns, I advise anyone interested to improve his skills design to look at these patterns.

When we search for design pattern articles, we found essentially documentation concerning “Gang of Four” patterns. They are very useful and contribute to well design the application.
But when I discovered GRASP patterns, I advise anyone interested to improve his skills design to look at these patterns; it gives a design fundamental rules.

Irrlicht is a 3D engine library that uses many GRASP concepts, let's discover with CppDepend the benefits of using this kind of patterns, and to show the using of GRASP concepts we will focus on the namespace irr::gui.

Creator:
To detect with CppDepend the existence of a creator, the easy way is to use a CQLinq query like this:

from m in Methods let depth0 = m.DepthOfCreateA("irr.gui.CGUISkin") where depth0 == 1 select new { m, depth0 }


The CGUIEnvironement is the only class that create CGUISkin.But what about the other GUI elements?
Almost all GUIElement are created by CGUIEnvironement class except CGUIButton.

from m in Methods let depth0 = m.DepthOfCreateA("irr.gui.CGUIButton") where depth0 == 1 select new { m, depth0 }



As we observe the CGUIButton is created in three different places, maybe it's better to have the same behavior as the other gui elements, and create it only in the CGuiEnvironement class.
The CGUIEnvironement class plays the role of creator, but what's the role of the CDefaultGUIElementFactory class?
The dependency graph between CDefaultGUIElementFactory and CGUIEnvironement shows the relation between them:


Adding GUIElement begin with the invocation of CDefaultGUIElementFactory::addGuiElement, and this method invokes the right creation method from CGUIEnvironement, it depends of the type of element to create.
Controller
The Controller for the GUIElements must at least manage event processing,this task is processed by CGuiEnvironement::OnEvent.
Let's discover which methods are used by OnEvent

from m in Methods where m.IsUsedBy ("irr.gui.CGUIEnvironment.OnEvent(constSEvent&)") select new { m }


The event fired is processed by a class that implements IEventReceiver.
Let’s search which classes implement this abstract class?

from t in Types let depth0 = t.DepthOfDeriveFrom("irr.IEventReceiver") where depth0 == 1 select new { t, depth0 }


What are the other responsibilities of CGUIEnvironement?
As we have seen before this class create the concrete classes and also manage event processing, and to discover if it has another responsibility we can search for methods used by this class:

from m in Methods where m.IsUsedBy ("irr.gui.CGUIEnvironment") select new { m }


It uses also classes from irr::io namespace to persist and load into xml files , so maybe this class has many responsibilities and it can impact its cohesion, but it still tolerable because this class has all data needed to persist data so this class follows the “Information Expert” principle of GRASP.

Low Coupling

Low coupling is desirable because a change in one area of an application will require less changes throughout the entire application. In the long run, this could alleviate a lot of time, effort, and cost associated with modifying and adding new features to an application.

Using abstract classes can improve the low coupling and we can evaluate the abstractness of a defined module by the following metric:

A = Na / Nc

Where:

* A = abstractness of a module Zero is a completely concrete module. One is a completely abstract module.
* Na = number of abstract classes in the module.
* Nc = number of concrete classes in the module.



The abstractness of Irrlich is equal to 0.1245972, and it contains 125 abstract classes.

And for irr::gui namespaces there are 28 abstract classes, and for each GUI element there's an equivalent interface.

from t in Namespaces.WithNameWildcardMatchIn("irr.gui").ChildTypes() where t.IsAbstract orderby t.NbLinesOfCode descending select new { t, t.NbLinesOfCode }


CppDepend provides the DSM view, and we can triangularize this matrix to focus under the red borders the highly dependent classes.


As we can observe all abstract classes are grouped, so we can consider them as module, and we can isolated them in another namespace or maybe in another project.
And to see the benefit of using abstract class to improve the low coupling, let’s search for classes that use the concrete class CGUISkin.

from t in Types where t.IsUsing ("irr.gui.CGUISkin") select new { t }



So only one class knows CGuiSkin, it’s his creator.And what about IGUISkin:

from m in Methods where m.IsUsing ("irr.gui.CGUISkin") select new { m }



Unlike CGUISkin the IGUISkin class is used by many other classes.
What about coupling between namespaces?

A dependency cycles exist between the namespaces, it can be not problematic but avoiding it enforce the loose coupling, and what’s interesting than the dependency cycles is the manner that namespaces interact with each other’s. When we choose to work with abstract classes, it’s recommended to interact between functional namespaces using abstract classes rather than concrete classes, except for namespaces that contains utility classes like irr::core.
Is Irrlich following this rule?
For that let’s see what the namespace “irr” uses as classes and methods.

from m in Methods where m.IsUsedBy ("irr") select new { m }


As we can see almost all interactions with other namespaces pass by abstract classes, except for irr::video::CVideoModelList and irr::scene::CMeshBuffer.
Let’s discover the origin of the dependency with irr::video::CVideoModelList, for that we can execute the following CQLinq query:

from m in Types.WithFullNameNotIn( "irr.video.CVideoModeList").ChildMethods() where m.IsIndirectlyUsing ("irr.video.CVideoModeList") select new { m }


The class irr::CIrrDeviceWin32 uses it because it declares a field as video::CVideoModeList instead of video::IVideoModeList.

High cohesion

The single responsibility principle states that a class should have more than one reason to change. Such a class is said to be cohesive. A high LCOM value generally pinpoints a poorly cohesive class. There are several LCOM metrics. The LCOM takes its values in the range [0-1]. The LCOMHS (HS stands for Henderson-Sellers) takes its values in the range [0-2]. Note that the LCOMHS metric is often considered as more efficient to detect non-cohesive types.

LCOMHS value higher than 1 should be considered alarming.



from t in Application.Types where t.LCOMHS > 0.95 && t.NbFields > 10 && t.NbMethods >10 && !t.IsGlobal orderby t.LCOMHS descending select new { t, t.LCOMHS, t.NbFields, t.NbMethods }


Only few classes are considered as no cohesive.

Conclusion: Irrlicht use namespaces to modularize the code base and abstract classes to improve the low coupling, so it makes it very easy to understand and maintain. And It’s a good example to follow if you want to improve your design quality.

Latest Jobs

Xbox Game Studios

Redmond, Washington
10.5.22
Technical Lighting Artist

Innogames

Hamburg, Germany
10.5.22
Game Designer - Elvenar

Six Foot

Houston, TX
10.3.22
Six Foot Director, Player Relations

Hometopia Inc.

Remote
10.7.22
Lead Engineer
More Jobs   

CONNECT WITH US

Explore the
Subscribe to
Follow us

Game Developer Job Board

Game Developer Newsletter

@gamedevdotcom

Explore the

Game Developer Job Board

Browse open positions across the game industry or recruit new talent for your studio

Browse
Subscribe to

Game Developer Newsletter

Get daily Game Developer top stories every morning straight into your inbox

Subscribe
Follow us

@gamedevdotcom

Follow us @gamedevdotcom to stay up-to-date with the latest news & insider information about events & more