General Responsibility Assignment Software Patterns (or Principles), abbreviated GRASP, consist of guidelines for assigning responsibility to classes and objects in object-oriented design.
Wikipedia
I want to start with the fact that, strangely, GRASP is in the shadow of SOLID patterns, although it seems to me that it has more specific examples and is easier to understand. The importance of these patterns can’t be overstated in modern development.
Generally, nine rules will suggest the right way to choose the correct responsibility for your classes. Patterns very helpful for the development, but in my opinion, they have a terrible structure. I’ve separated these rules into two groups: roles and principles.
Principles
Let’s start with principles. Roles will realize these principles, and generally, they as four pillars on which role responsibilities stand.
Low coupling
Problem: How to reduce the impact of change? How to support low dependency and increased reuse?
Solution: Assign responsibilities, so that (unnecessary) coupling remains low. Use this principle to evaluate alternatives.
Craig Larman
Coupling is the method and degree of interdependence between software modules, the strength of the relationships between modules, a measure of how interdependent different routines or modules are.
High coupling is considered a serious drawback, as it makes it difficult to understand the logic of modules, their modification, offline testing, and reuse individually. By contrast, Low coupling is the hallmark of a well-structured and well-designed system, and when combined with high cohesion, it meets the overall readability and maintainability scores.
In simple terms, the fewer connections between components, the better.
High cohesion
Problem: How to keep objects focused, understandable, manageable, and as a side effect support Low Coupling?
Solution: Assign a responsibility so that cohesion remains high. Use this to evaluate alternatives.
Craig Larman
Cohesion is a measure of the strength of the interconnectedness of elements within the module; the manner and extent to which the tasks performed by some software modules are related.
A low cohesion class performs many heterogeneous functions or unrelated responsibilities. It is undesirable to create such classes because they lead to the following problems:
An object (subsystem) is considered high cohesion if its responsibilities are well coordinated with each other, and it does not perform huge amounts of work.
Actually, methods inside the object must do similar operations. If your methods do different operations, then you need to decomposite it into a few separate objects.
Polymorphism
Problem: How to handle alternatives based on type?
Solution: When related alternatives or behaviors vary by type (class), assign responsibility for the behavior (using polymorphic operations) to the types for which the behavior varies.
Craig Larman
It would be best if you have created your system to swap the current implementation on another easily. For example, you can use the dependency injection design pattern and then extend these classes or better change the type to interface and implement the realization.
Protected variations
Problem: How to design objects, subsystems, and systems so that the variations or instability in these elements don’t have an undesirable impact on other elements?
Solution: Identify points of predicted variation or instability; assign responsibilities to create a stable interface around them.
Craig Larman
This is the most important principle that is indirectly related to the rest of the GRASP principles. Currently, one of the crucial software metrics is the ease of change. As architects and developers, we must be ready for ever-changing requirements. This is not optional and “nice to have” quality attribute – it is a “must-have” and our duty.
Roles
The more straightforward part of these patterns is roles. The best way it avoids classes with a few positions. I know what creating a new class and moving some methods into it is mentally so complicated, but it needs to do.
Information expert
Problem: What is a basic principle by which to assign responsibilities to objects?
Solution: Assign responsibility to the class that has the information needed to fulfill it.
Craig Larman
An expert can be absolutely any class that has the necessary information. For example, we’ve got an EBook
entity if we need information about the amount of all EBooks
to need to create the next class EBooks
.
<?php
class EBooks {
public function get_total(): int {}
}
Creator
Problem: Who creates object A?
Solution: Assign class B the responsibility to create object A if one of these is true (more is better)
– B contains or compositely aggregates A
– B records A
– B closely uses A
– B has the initializing data for ACraig Larman
Sounds straightforward, and I think understandable for each development. For example, you can use factories or any form of generative design patterns.
Let’s add the simple EBookFactory
factory that will create all entities:
<?php
class EBookFactory {
public function create_ebook( $author, $category, $pages ): EBook {
return new EBook( $author, $category, $pages );
}
}
Controller
Problem: What the first object beyond the UI layer receives and coordinates “controls” a system operation?
Solution: Assign the responsibility to an object representing one of these choices:
– Represents the overall “system”, “root object”, device that the software is running within, or a major subsystem (these are all variations of a facade controller)
– Represents a use case scenario within which the system operation occurs (a use case or session controller)Craig Larman
The first object that deals with the user interface and delegates which object will be next in the process.
Who are using any frameworks could have known it as Controller from MVC pattern or Presenter in MVP.
The main idea is to make a separate entity and view template.
<?php
class EBooksController {
public function index( EBook $ebook ) {
require 'ebook.php';
}
}
Pure fabrication
Problem: What object should have the responsibility, when you don’t want to violate High Cohesion and Low Coupling but solutions offered by other principles are not appropriate?
Solution: Assign a highly cohesive set of responsibilities to an artificial or convenience class that doesn’t represent a problem domain concept.
Craig Larman
Fabricated class/artificial class – assign a set of related responsibilities that don’t represent any domain object. In simple words, it’s something that doesn’t exist in the business logic.
For example, we want to get an entity from a database, and for this, we could create a EBookRepository
class:
<?php
class EBookRepository {
private $db;
public function __construct( PDO $db ) {
$this->db;
}
public function get_by_id( $id ): array {
$ebook = $this->db->query(
sprintf(
'SELECT * FROM ebooks WHERE ID = %d',
(int) $id
)
);
return $ebook->fetch( PDO::FETCH_NUM );
}
}
Indirection
Problem: Where to assign a responsibility to avoid direct coupling between two or more things?
Solution: Assign the responsibility to an intermediate object to mediate between other components or services to not directly coupled.
Craig Larman
Indirection introduces an intermediate unit to communicate between the other units so that the other units are not directly coupled. Generally, all roles using this principle.
More objects could help you create a more flexible and scalable solution. As you can notice, Controller, Creator, Information Expert were used to decrease the coupling between modes, view, external API.
Let’s add a mediator between EBookRepository
and database:
<?php
class EBookQuery {
private $db;
public function __construct( PDO $db ) {
$this->db;
}
public function get_by_id( $id ): array {
$ebook = $this->db->query(
sprintf(
'SELECT * FROM ebooks WHERE ID = %d',
(int) $id
)
);
return $ebook->fetch( PDO::FETCH_NUM );
}
}
class EBookRepository {
private $query;
public function __construct( EBookQuery $query ) {
$this->query = $query;
}
public function get_by_id( $id ): EBook {
$ebook = $this->query->get_by_id( $id );
return new EBook( $ebook->author, $ebook->category, $ebook->pages );
}
}
Summary
A flexible and scalable solution is an art of software architecture, and GRASP patterns could help with your classes’ responsibilities. Generally, the GRASP principles look more practical and easier to implement than SOLID. Also, a lot of them are using popular frameworks as in PHP language as in others.