BLOG
SOLID
The SOLID principles are five fundamental object-oriented design rules that help developers create code that is easier to understand, maintain, and extend.
SRP
Single Responsibility Principle
The SRP states that a given class should be responsible for only one functionality. This doesn’t mean it can only have one method; it can have multiple methods as long as they are related to that specific functionality. For example, consider a program that converts Celsius to Fahrenheit and vice versa. The incorrect code presents an Assistant
class with a single method that performs four tasks: converting Celsius to Fahrenheit, Fahrenheit to Celsius, and displaying the results. The correct code is split into classes where one handles temperature conversions and another is responsible for displaying the results.
OCP
Open/Closed Principle
The Open/Closed Principle (OCP) states that classes, modules, or functions should be open for extension but closed for modification. This principle enforces the use of abstraction, as there should be a clear separation between shared and specific parts within a program. The shared part should be encapsulated in an interface or abstract class, while the specific part should reside in concrete classes that implement the interface or inherit from the abstract class. The incorrect code shows a single Figure
class with a method that calculates the perimeter of a square and a rectangle. To add another shape, we would need to modify the entire method. In the correct code, a Figure
interface is created, implemented by each specific shape. This setup allows new shape classes to be added easily without altering the existing interface.
LSP
Liskov Substitution Principle
The Liskov Substitution Principle (LSP) states that if inheritance is used in code, any subclass should be able to replace its superclass without affecting the program’s functionality. This means that interface compatibility, as well as all methods, must be preserved. The incorrect code shows a superclass Bird
and two subclasses, Crow
and Ostrich
, which extend it. However, the Ostrich
class cannot substitute for the superclass because Bird
requires implementing a fly()
method. This method is not suitable for ostriches, as they are flightless birds. The correct code also contains a superclass Bird
, but the fly()
method is removed. Now, Ostrich
can extend Bird
without issue. Meanwhile, Crow
inherits from FlyingBirds
, a subclass of Bird
that includes the fly()
method.
ISP
Interface Segregation Principle
The Interface Segregation Principle (ISP) states that it is better to create several smaller, specific interfaces rather than one large, general one. This approach avoids the problem of implementing methods that a class does not need. I’ll illustrate this with a pizza example. In the incorrect code, there is a single Pizza
interface, which is implemented by all types of pizzas. As a result, the MargheritaPizza
, SalamiPizza
, and FruttiDiMarePizza
classes have methods they don’t use—for instance, FruttiDiMarePizza
should not have a method for adding salami, and SalamiPizza
shouldn’t have methods for adding seafood. In the correct code, the large Pizza
interface is divided into smaller interfaces: Pizza
, Meat
, and Seafood
. Now, each pizza type implements the basic Pizza
interface and either the Meat
or Seafood
interface, depending on its type.
DIP
Dependency Inversion Principle
The Dependency Inversion Principle (DIP) states that high-level modules should not depend on low-level modules; instead, both should depend on abstractions (using interfaces and abstract classes). Furthermore, abstractions should not depend on details; rather, details should depend on abstractions. In the incorrect code, the Library
class is a high-level module that depends directly on the BookRepository
class, a low-level module. In the correct code, BookRepository
implements a Repository
interface, so the Library
class now depends on the Repository
interface rather than directly on BookRepository
. This way, the library depends on an abstraction, the Repository
interface, which prevents changes in the low-level module from affecting the high-level module.