Irrlicht is a 3D engine library that use many GRASP concepts, let's discover with CppDepend the benefits of using this kind of patterns, and to show using of GRASP concepts we will focus on the namespace irr::gui.
To detect with CppDepend the existence of creator, the easy way is to use a CQL query like this:SELECT METHODS WHERE DepthOfCreateA "irr.gui.CGUISkin" == 1
So the CGUIEnvironement is the only class that create CGUISkin, but what about other GUI elements? almost all GUIElement are created by CGUIEnvironement class except CGUIButtonSELECT METHODS WHERE DepthOfCreateA "irr.gui.CGUIButton" == 1
As we observe the CGUIButton is created in three different places, maybe there's a reason for this behavior. So the CGUIEnvironement class act as creator, but what's the role of 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 invoke the right creation method from CGUIEnvironement, it depends of the type of element to create. Finally the creation responsability and logic for GUI elements is isolated in two classes.
The Controler for GUIElements must at least manage event processing,this task is processed by CGuiEnvironement::OnEvent. Let's see which methods are used by OnEventSELECT METHODS WHERE IsUsedBy "irr.gui.CGUIEnvironment.OnEvent(constSEvent&)"
So the event fired is processed by a class that implement IEventReceiver. Which classes implement this abstract class?SELECT TYPES WHERE DeriveFrom "irr.IEventReceiver" ORDER BY DepthOfDeriveFrom
What's the other responsabilities of CGUIEnvironement? As we have seen before this class create the concrete classes and also manage event processing, and to discover if it do another responsability we can search for methods used by this class:SELECT METHODS WHERE IsDirectlyUsedBy "irr.gui.CGUIEnvironment"
This class use also classes from irr::io namespace to persist and load into xml files , so maybe this class has many responsabilities and it can impact it's cohesion, but it still tolerable because this class has all informations needed to persist data so this class follows the "Information Expert" principle of GRASP.
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
* A = abstractness of a modulke 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's 28 abstract classes , for each GUI element there's the equivalent interface.SELECT TYPES FROM NAMESPACES "irr.gui" WHERE IsAbstract
CppDepend provides DSM graph, and we can triangularize this matrix to focus under red borders highly dependent classes, and to detect modules.
As we can observe all abstract classes are grouped, so we can consider them as module, so 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.SELECT METHODS WHERE IsDirectlyUsing "irr.gui.CGUISkin"
Only one class know this class, it's his creator. And what about IGUISkin:SELECT METHODS WHERE IsDirectlyUsing "irr.gui.CGUISkin"
Unlike CGUISkin the IGUISkin class is visible and used by many classes. What about coupling between namespaces:
A dependency cycle exist between namespaces, having this dependency can be not problematic but avoiding it enforce loose coupling, but what's interesting than this dependency cycle is the manner that namespaces interact with each others. 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 follow this rule? For that let's see what the namespace irr use as classes and methods.SELECT METHODS WHERE IsDirectlyUsedBy "irr"
As we can see almost all interaction 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 CQL query:SELECT METHODS OUT OF TYPES "irr.video.CVideoModeList" WHERE IsUsing "irr.video.CVideoModeList"
so the class irr::CIrrDeviceWin32 use it because this class declare a field as video::CVideoModeList instead of video::IVideoModeList.
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.SELECT TYPES WHERE LCOMHS > 0.95 AND NbFields > 10 AND NbMethods >10 AND !IsGlobal ORDER BY LCOMHS DESC
Only few classes are considered as no cohesive.
Irrlicht use namespaces to modularize the code base and absract classes to improve low coupling, so it makes it very easy to understand and maintain. It's a good example to follow if you want to improve your design quality.