SOLID principles in GO with examples
Introduction
The SOLID principles are a set of five design principles that help developers build code that is easier to maintain, test and extend.
These principles were introduced by Robert C. Martin, in his paper Design Principles and Design Patterns
SOLID stands for:
S: Single Responsibility Principle (SRP)
O: Open/Closed Principle (OCP)
L: Liskov Substitution Principle (LSP)
I: Interface Segregation Principle (ISP)
D: Dependency Inversion Principle (DIP)
Here are explanations and examples of each SOLID principle using the GO programming language.
1. Single Responsibility Principle (SRP):
A class or module should have only one reason to change.
This means that each class or module should be responsible for a single, well-defined task.
Bad example
On lines 13 and 21, we can see that the code is calculating the area and printing the result, which violates the single responsibility principle.
Good example
Above code defines separate responsibilities. The square and circle types define the method area, which calculates and returns the area value, while the type outputter defines how to generate the string output.
How to apply SRP in Go
A good example of the SRP in Go is the use of structs. A struct can be used to group related data and methods. This makes it easy to create classes or modules that have a single responsibility.
2. Open/Closed Principle (OCP):
Software entities (classes, modules, functions, etc.) should be open for extension but closed for modification.
This means that new features can be added to the software without changing the existing code.
Bad example
In the above example, the calculator type's method areaSum defines a parameter shapes of the interface[] type. In the lines ahead, we can find a switch case to map each possible type.
when we define a new shape triangle, for example, we will need to modify the areaSum method to accommodate it which will violate OCP.
Good example
The shape interface above defines the method area and each struct type implements it. In addition, the sumAreas method now accepts parameter shapes of shape[] type. The switch case has been removed and in its place, the code executes the area method for each shape.
How to apply OCP in Go
The OCP can be applied to Go by using interfaces. An interface is a contract that defines a set of methods that a type must implement. This makes it easy to add new features to a program without having to change the existing code.
3. Liskov Substitution Principle (LSP):
Derived classes should be substitutable for their base classes.
This means that any code that works with a base class should also work with a derived class without any problems.
Example
The above example satisfies LSP as teacher type and student type could be substituted by the human type
How to apply LSP in Go
The LSP can be applied to Go by using composition. When a type composes another type, it has access to the methods that it needs. This ensures that any code that works with the base class will also work with the derived class.
4. Interface Segregation Principle (ISP):
No client should be forced to depend on methods it does not use.
This means that interfaces should be designed to be as specific as possible so that clients only have to depend on the methods that they need.
Bad example
In the above example, the square doesn't need the volume method because it is a flat shape, only a cube needs the volume method because it is an object shape.
Square type is still forced to implement method volume, thus violating ISP.
Good example
In the above example square implements area() and cube implements area() and volume(). Also, areaSum and areaVolumeSum functions are changed to accept the correct types.
With these changes, no type is forced to define a method that isn't used by that type.
How to apply ISP in Go
The ISP can be applied to Go by using composition. When a type composes another type, it only has access to the methods that it needs. This prevents the type from being forced to depend on methods that it does not use.
5. Dependency Inversion Principle (DIP):
High-level modules should not depend on low-level modules. Both should depend on abstractions.
This means that high-level modules should not depend on specific implementations of low-level modules. Instead, they should depend on abstractions that represent the functionality of the low-level modules.
Bad example
In the above example on line 26, UsersRepository uses struct type to access query methods, which violates the DIP.
Good example
The above example creates a new interface called DBConn to define database methods. The UsersRepository has been modified to use an abstraction interface type instead of a struct type.
How to apply DIP in Go
The DIP can be applied to Go by using dependency injection. Dependency injection is a technique where a type depends on an abstraction instead of a concrete implementation. This makes it easy to change the implementation of the dependency without having to change the type that depends on it.
Summary
The SOLID principles are a valuable tool for creating better software. By following these principles, you can create code that is more modular, flexible, and easier to maintain.