GRASP - General Responsibility Assignment Software Principles
GRASP is an acronym for General Responsibility Assignment Software Principles. In this article, we want to point out these principles and how they work.
This collection of object-oriented design rules goes back to Craig Larman and his book Applying UML and Patterns from 2004. Larman didn't invent any of these principles and ideas. He simply collected them.
GRASP is a pretty fancy name, but it somehow feels more like he wanted GRASP as the acronym and linked random words to reach it. Even though it is about to become an oldie, most rules collected under the GRASP banner are still helpful, and they should be essential to every developer's toolkit.
The General Responsibility Assignment Software Principles list nine principles:
- Information Expert
- Low Coupling
- High Cohesion
- Protected Variations
- Pure Fabrication.
Though they are tailored to object-oriented design, some also apply for general software development as well.
I write about some things that are not from Larman's book but my own opinion and personal note. If you don't want the unaltered version, I suggest you read the original book: Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development
The controller pattern is a common tool in object-oriented software design. Though most people might only associate the controller with the ancient architecture of the MVC (Model-View-Controller - Which is far too often used for cases it absolutely doesn't fit, but that's a story for a different day), the controller is a construct of itself.
As the name implies, the controller's work is to 'control' any events that are not directly related to the user interface. But controlling does not mean implementing. The controller consists of barely any logic. It is merely a mediator between the presentation layer and the (core) logic.
For example, if we imagine a simple web-shop application with the use case of "User X bought item A"; The controller's job would be to receive the signal of the pressed button from the UI and then run the necessary functions in the correct order. In the example at hand that could be to certify the payment and then initialize the shipment of the item.
Applying the principle of a controller can hugely improve your software's lifetime as it naturally creates a resilient layer regarding the interchangeability of code. If your UI or logic (unlikely, but possible), you can adjust your mappings instead of having to rewrite large chunks of code.
Also, it becomes incredibly easier to add other layers, such as an app at the overlaying presentation layer that uses a different UI but wants the same responses.
You can also think of a controller as a driver. It knows both parts it connects with one another but it acts merely as a broker. It consists of just a few and essential parts of code.
The creator is another pattern, but to me, it's more like an abstract idea than a real pattern. I must admit I rarely use this pattern intentionally.
A creator is a class that is responsible for creating instances of objects. Larman defines the following cases as B is the creator of A:
- B aggregates A objects
- B contains A objects
- B records instances of A objects
- B closely uses A Objects
- B has the initializing data that will be passed to A when it is created
So an example for a creator could be a library that contains books. In that case, the library would be the creator of books, even though this sounds syntactically weird as natural language.
Indirection isn't a pattern but an idea. It is also of no use of its own, only in combination with other ideas such as low coupling. The goal behind indirection is to avoid direct coupling.
To pick up an earlier example the controller for example is a kind of indirection between the UI and the logic. Instead of coupling the UI directly to the logic, the controller decouples this layer and thereby comes with all the advantages of indirection.
Another principle that falls into that area is the information expert. Sometimes it is just called the expert or the expert principle. The problem that should be solved with the information expert is the delegation of responsibilities. The information expert is a class that contains all the information necessary to decide where responsibilities should be delegated to.
You identify the information expert by analyzing which information is required to fulfill a certain job and determining where the most of this information is stored.
Low or loose coupling is the idea of creating very few links between modules. In GRASP it is an inter-module concept. Meaning it describes the flow between different modules, not the flow inside the modules.
Together with cohesion, it is the main reason upon which you make global design decisions such as "Where do we pack this module or function to?".
Low coupling is another concept that supports interchangeability. By making very few dependencies, very few code must be changed in case of a change.
in GRASP high (functional) cohesion is an intra-module concept. Meaning it describes the flow inside a certain module not the flow between modules.
The main reason behind high cohesion is the idea of reducing complexity. Instead of building large classes that have many functions that have few in common and are hard to maintain, the goal should be to create classes that fit exactly their defined purpose.
It's kind of hard to understand these abstract ideas but I also wasn't able to come up with a simple example, which is why I will write an extra article on the topic of low cohesion combined with high cohesion that is more detailed.
πολυ (polús)= many/multi,
μορφή (morphé) = shape/form,
ισμός (ismós) = imitation of.
Meaning, Polymorphism could be frankly translated as "something that imitates many forms". And that might be a concise but useless explanation to someone who has never heard of polymorphism.
Anyone who ever took a programming class is most likely familiar with polymorphism. But, it can be a tricky question to define it sensefully.
The idea again is to reduce complexity by imagining that objects follow simple and similar rules. Consider the following example: You have three objects A, B and C. B has the same methods and attributes but has an additional method X. C has the same methods and attributes as B, but an additional method Y.
The non-developer approach to this problem would be: "Great, as you said, we have 3 objects, so we have A, B, and C. Problem solved. You little stinky moron.". Yes, non-developers are insanely evil creatures.
class A:attribute_a: strattribute_b: intattribute_c: floatdef being_useless(self):print("I am being of no use.")class B:attribute_a: strattribute_b: intattribute_c: floatdef being_useless(self):print("I am being of no use.")def X(self):print("I am Batman.")class C:attribute_a: strattribute_b: intattribute_c: floatdef being_useless(self):print("I am being of no use.")def X(self):print("I am Batman.")def Y(self):print("I am expensive to my parents.")
But as a programmer, you instinctively know that we need the concept of inheritance here. A is an object, B is an object that inherits from A, and C is an object that inherits from B. Caution: Inheritance is not polymorphism - inheritance is an application that is allowed due to polymorphism.
class A:attribute_a: strattribute_b: intattribute_c: floatdef being_useless(self):print("I am being of no use.")class B(A):def X(self):print("I am Batman.")class C(B):def Y(self):print("I am expensive to my parents.")
To wrap it up: polymorphism is the concept of disassembling objects and ideas into their most atomic elements and abstracting their commonalities to build objects that can act like they were others. Not only to reduce complexity but also to avoid repetitions.
The protected variations pattern is a pattern used to create a stable environment around an unstable problem. You wrap the functionality of a certain element (class, interfaces, whatever) with an interface to then create multiple implementations by using the concept of polymorphism. Thereby you are able to catch specific instabilities with specific handlers.
An example of the protected variations pattern is working with sensory data, such as a DHT22, a common temperature and humidity sensor often used for Raspberry Pi or Arduino). The sensor is doing its job pretty well, but sometimes it will say the temperature just rose by 200 celsius or won't return any data at all. These are cases you should catch using the protected variations pattern to avoid false behavior of your program.
To reach low coupling and high cohesion, it is sometimes necessary to have pure fabrication code and classes. What is meant by that is that this is code that does not solve a certain real-world problem besides ensuring low coupling and high cohesion. This is often achieved by using factor classes.
Some last words#
Often when I look into programming principles, I feel mildly overwhelmed and discouraged. That's in part because some of these concepts are very hard to grasp if you never heard of them or got no practical use case in mind. But with a growing set of programming principles and best practices, you will see that all these principles refer to the same key statements and follow the same rules.
Unfortunately, in my opinion, there's also no substitution for learning it the hard way. Memorizing merely the key statements will eventually lead to greater school and university scores, but it will still be useless incoherent information without any practical use. Thereby, my advice is to learn as much as possible about code and its principles but also get as much hands-on experience as possible to abstract the key statements by yourself.
If you liked this post or are of any different opinion regarding any of the written, please let us know via mail or comment. Happy coding!