- Published at: https://www.fluentcpp.com/2021/06/23/grasp-9-must-know-design-principles-for-code/
- Author: Jonathan Boccara
- Publication date: June 23, 2021
In order to write code that is understandable, maintainable and that stands the test of time, one of the crucial skills that we all need to have is design.
What does code design mean? In my definition, doing code design means deciding which class (or more generally which component) is in charge of which responsibility.
The reason why this is so crucial is because, according to the above definition, code that is well designed is consistent and well organised, and code that is poorly designed is essentially a mess.
And understanding, adding or fixing something in a well organised structure is, as you can imagine, easier that in a mess.
The need for Patterns
Being able to decide where to assign any given responsibility is a make-or-break skill for your code. But how do we do that?
With Experience, of course!
After years of trials and errors, after paying the high prices of your design mistakes, after living in a messy code, you will end up realizing that some design choices tend to work better than others.
Or there is an easier way: capitalizing on the experience of others.
The question of choosing where to assign a responsibility in code has been around for decades, and has been thought over by thousands and thousands of developers in their day-to-day code. Probably thousands and thousands of mistakes have been made, and as many lessons have been drawn from those mistakes.
If we could benefit from this
std::accumulated experience, then we would stand on the shoulders of giants.
Luckily for us, a lot of this wisdom is available to us, and what’s more, synthesized into manageable bits that we can apply in our everyday life while coding: design principles.
Design principles are guidelines that help us take the right decisions when assigning responsibilities in our code.
There is a set of 9 such design principles: the GRASP principles.
The book where I learnt the design principles
GRASP stands for General Responsibility Assignment Software Principles. I think the words that carry the most meaning in this acronym are RA: Responsibility Assignment. This is exactly what we’re talking about.
I learnt those principle in Craig Larman’s book Applying UML and Patterns:
Even though the book title mentions UML, the book is about object-oriented design in general, as Martin Fowler praised (this is on the book cover): “People often ask me which is the best book to introduce them to the world of OO design. Ever since I came across it, Applying UML and Patterns has been my unreserved choice.”
I recommend that you read at least the parts about the GRASP patterns in this book, if not all of it.
Let me go further: even if you should read both, I think that GRASP principles are a more useful reading than the popular GoF design patterns.
Why such a bold statement?
- The GRASP principles will let you understand the rationale behind the GoF design patterns,
- Some GoF design patterns are obscure and not used often (at least in my experience, take the “Bridge” pattern for example),
- Some GoF patterns should be avoided most of the time (for example the “Singleton” pattern, which is essentially a global variable with no copy constructor),
- You can apply the GRASP pattern to other things than classes. And with free functions being idiomatic to C++, this is particularly relevant for C++ developers.
Ssome of the GoF design patterns are indeed necessary to know (“Strategy” for example), so you should also read the GoF book (especially since it contains more than just a design patterns catalogue). But to me, GRASP is a more fundamental topic to read about.
The GRASP patterns
Let’s see what the 9 GRASP patterns are. I’ll make a brief summary of each one, and you can refer to the above book for more detailed discussions and examples.
Let’s start by one that is quite natural: information expert. This principle recommends that if you have an operation to do, and this operations needs inputs, then you should consider putting the responsibility of carrying out this operation in the class that contains the inputs for it.
This helps keeping the data local, because if you put the operation somewhere else then the inputs will have to be carried over there, creating a relationship between the class that holds the input data (the “information expert”) and that new place. This would create coupling and be detrimental for encapsulation, thus creating complexity.
Granted, it’s a natural thing to do. But formalizing this as a principle still has several benefits. For example, if you feel that a design is not quite right, thinking about the information expert principle can help you pinpoint what was disturbing you in it. Also, it helps expressing your remarks in a code review.
The Creator principle gives guidelines as to which class B should be in charge of creating a certain type of objects A. The principle contains a set of rules, such as:
- B contains or aggregates instances of A
- B closely uses A
- B has the inputs to construct A
- and so on
The more rules are fulfilled, the better B is suited to instantiate objects of type A.
This goes a further than Information expert: it’s not just about having the input of the operation of construction, it’s about being “close” to the operation.
If you put together two part of the code that are semantically close (the construction of A, and the code that works a lot with A), then they become easier to reason about than if they were far apart.
Coupling happens between two parts of the code when one depends on the other. Coupling introduces complexity, if only because the code can then no longer be understood isolation.
Such dependencies can be explicit, in terms of a function call for example, and that’s unavoidable and often OK.
But other types of coupling are less sane and less visible, for example when a part of the code expects that another part has set a data member to a certain value.
The design principle of low coupling encourages to keep coupling low, and it can help in particular to choose between two designs: select the one that introduces the lower amount of coupling.
For more details about the various types of coupling and the various types of problems that coupling generates, refer to this dedicated article on coupling.
The principle of Protected variations is related to the one of Low coupling, because it helps reducing the impacts of the changes of the code of one part A on another part B. The code of part B is protected against the variations of the code of part A, hence the name of the pattern.
How do you achieve such a protection? By organizing the responsibilities around stable interfaces.
This is particularly relevant for code that tends to change often. Introducing an interface between this unstable part of the code and the rest of the codebase helps limiting the rippling effects of those frequent changes.
The principle of protected variations is, according to my understanding, very close to the concept of “anti-corruption layer” from Domain Driven Design.
The Indirection pattern is another way to reduce coupling by creating an intermediary class (or any kind of component) between two classes A and B. This way, the changes in each one of A and B don’t affect the other one. The intermediary class absorbs the impact by adapting its code rather than A or B (or more other classes).
This relates a lot to the Adapter design pattern, even though the Adapter design pattern is rather made to connect two existing incompatible interfaces. But it also has the effect of protecting each one against the changes of the other.
Indirection looks a bit like Protected variations, as they both introduce a layer between components in order to reduce coupling.
According to my understanding, the difference between Protected variations and Indirection is that Protected variations is about designing interfaces in the existing components, whereas Indirection is about introducing a new component in the middle.
The Polymorphism principle is an exhortation to use… polymorphism in your code.
Polymorphism is often seen as having a base class with virtual methods that defines an interface, and derived classes implementing this interface, but this is only one type of polymorphism. There are plenty of types of polymorphism (including templates for example) and I hope we’ll get to dive into this topic at some point on Fluent C++.
The usage for polymorsphism is when there are several ways to accomplish a task, and you want to decouple the clients of this task from the various pieces of code that implement the various ways to perform it.
The Polymorphism principle is very close to the GoF Strategy pattern, if not identical. It contributes to the Low Coupling principle.
The principle of High cohesion encourages to focus classes around one responsibility, and to have all its components oriented towards achieving this responsibility. This is the principle of “do one thing and do it well”.
The principle of high cohesion also applies to other elements of the code, such as functions, and also modules and systems.
This ties back well with the idea of having a well-organized code: a highly cohesive component lets you label it in your mind with “this is the part of the code that does X”. This allows better mental abstractions as well as code abstractions.
It is natural to represent in our code objects that map the reality of the domain that we’re trying to model. For example, if you’re working on a financial application, you can probably expect to encounter some classes called
Portfolio in the code.
But sometimes you have a responsibility to assign, and it seems to not fit well in any domain class. And according to the principle of High cohesion above, you shouldn’t force a responsibility into a class that is already doing something else.
That’s when the principle of Pure fabrication comes into play: create a class that does not map to a domain object, and let it achieve this new responsibility in a cohesive way.
This principle is probably one of the hardest to guess, because it goes against our natural tendency to write code that represents the problem we’re trying to solve.
Here is an example of a pure fabrication: a UI Controller. This is such a common pattern that it was included in the 9 GRASP principles. Personal opinion: I would have kept this as an example of pure fabrication and not made a principle out of it.
The controller is the first non-UI component that receives the UI event and organizes the operations to react to this event. Indeed, that doesn’t map to any domain object, even if the UI itself can display domain concepts.
There are also other examples of pure fabrications in the GoF design patterns: for example, a Facade object is a pure fabrication.
Some principles are more fundamental than others
These were the 9 GRASP principles:
- Information expert
- Low coupling
- Protected variations
- High cohesion
- Pure fabrication
Those principles are not all of the same type. Some of those are overarching principles of good design. Some are techniques to put those principles into practice. Some are merely examples.
Also, they relates to some GoF design patterns, sometimes closely and sometimes the GoF patterns are implementations of the GRASP patterns.
In the next post, we will try to relate the GRASP patterns together.