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.