Design Patterns, GRASP and SOLID

Alana Brandão
4 min readOct 18, 2021

--

In the last article we talked about Domain-Driven Design with examples of how to identify contexts and think about the tactical design. A good way to apply DDD is also paying attention to the design patterns, and the first example we will see here is using SOLID principles.

SOLID Principles

The word SOLID is an acronym of five design principles introduced by Robert C. Martin, also know in the dev community as Uncle Bob:

S: Single responsibility

O: Open-closed

L: Liskov Substitution

I: Interface Segregation

D: Dependency Inversion

Thinking about the contexts presented in the “Golden Cine” business case, we can now talk about a few ways of applying SOLID principles to the tactical design:

Single Responsibility Principle

This principle stands that a class must have only one objective, one responsibility. It refers to cohesion, with a class having only one reason to exist, not involving more than one responsibility.

The ticket purchase process involves the ticket sale itself, the payment process, and the operational part that a ticket sale represents. We can have a SalesService class that is called when a ticket is purchased. SalesService calls PaymentService, responsible for only handling payments and also a SoldTicketService, to control the number of sold tickets for a given show. Each service will be responsible for handling only one kind of process.

Dependency Inversion Principle

This principle was defined by Robert Matin as:

High-level modules should not depend on low-level modules. Both should depend on abstractions. Abstractions should not depend on details. Details should depend on abstractions.

It means that we should depend on interfaces or abstract classes instead of concrete classes.

On the ticket purchase flow SalesService calls PaymentService (so the customer can perform payment) and SoldTicketsService (to control the number of sold tickets). In this flow, SalesService depends on two other services, and if we use concrete classes for it we will have a high coupled structure, where one class depends on another:

High coupled services

To improve this implementation and apply the Dependency Inversion Principle we can change it, so instead of depending on concrete classes, we can depend on abstractions.

Low coupled services

This way we can reduce the dependency and coupling between classes by introducing interfaces.

GRASP patterns

The GRASP patterns (General Responsibility Assignment Software Patterns) include a set of principles and concepts to help in the construction of applications using object-oriented programming. They list the responsibilities of classes and objects and were created to make the code more flexible and extensible, making it easier to maintain. There are nine patterns:

· Creator

· Information Expert

· Low Coupling

· High Cohesion

· Controller

· Polymorphism

· Pure Fabrication

· Indirection

· Protected Variations

On the “Golden Cine” business case there is a high coupling between contexts, with Sales context notifying Operational context when a ticket is sold. When changing the classes to use interfaces we are applying the principle of Low Coupling, reducing dependency between contexts. Also, we are applying the Protected Variations pattern, so each team can work in the implementation of concrete classes without affecting the other context, which will depend only on the interfaces definition.

Another approach that could be used to increase cohesion and reduce coupling would be the introduction of the Strategy Pattern. This pattern can be implemented when an algorithm has a complex logic that can be separated into different classes, and those classes can be interchangeable. We can introduce an interface to provide the abstraction and include the implementation details in derived classes.

Payment service for instance could have an algorithm that receives the payment type and does the logic to accomplish payment according to it.

public class PaymentService{

public String payTicket(String paymentType){
if("WIRE_TRANSFER".equals(paymentType)){
//pay using a wire transfer
return "Payment performed successfully";
}else if("CREDIT_CARD".equals(paymentType)){
//pay using credit card
return "Payment performed successfully";
}
return "Payment error";
}

}

A better way to do it is applying the Strategy Pattern, segregating the implementation into specific instances. Each one will be responsible for performing the process and validations for each payment type.

interface PaymentService{
String payTicket();
}
class PaymentServiceCreditCard implements PaymentService{
public String payTicket() {
//pay using credit card
return "Payment performed successfully";
}
}
class PaymentServiceWireTransfer implements PaymentService{
public String payTicket() {
//pay using a wire transfer
return "Payment performed successfully";
}
}

When projecting a system and thinking about its architecture we need to consider if adding GRASP or any other design pattern is something really necessary, to avoid something called “overengineering”. This means developing a code that is more complex or has more functionalities than necessary, a code where we are trying to solve problems that don’t even exist. We must apply design patterns when we need them, to solve real problems and limitations.

Next article we will talk about clean code and refactor techniques we can apply using the same business case.

--

--

Alana Brandão

Brazilian coding girl, Java Developer with 10 years of experience. Technical leader, growing up in the architectural area.