SOLID : The First 5 Principles in Object Oriented Design

Tram Ho

SOLID is an acronym for the first five principles of object-oriented design (OOD) by Robert C. Martin.

Using these principles together enables the developer to create maintainable and extensible software, and helps developers easily avoid code smells and refactor code. This is also part of the agile principle.

Note: This is just a simple article that introduces readers to SOLID without going into details.

SOLID stands for:

S – Single-responsiblity principle O – Open-closed principle L – Liskov substitution principle I – Interface segregation principle D – Dependency Inversion Principle

Let’s dissect each principle to understand why SOLID can help us become a “harder” dev.

Single-responsiblity principle (SRP)

SRP states:

Each class should only have a single reason that can change it, ie each class should only be responsible for a specific task.

For example, suppose we have several shapes and we want to sum all areas of multiple shapes. This is quite simple right?

First, we create the classes corresponding to the shapes and let the constructor set the required parameters. Next, we create the AreaCalculator class and then write the logic that calculates the total area of ​​all the provided shapes.

To use the AreaCalculator class, we simply need to instantiate the class and pass an array of shapes and show the output at the bottom of the page.

The problem with this method is that we use the AreaCalculator class to process logic to export data. What if the user wants to export the data in json format or something else? All of that logic will be handled by the AreaCalculator class, which is against the SRP principle. The AreaCalculator class should only sum the area of ​​the provided shapes, it should not care if the user wants json or HTML. So, to overcome this, we can create a SumCalculatorOutputter class and use this class to handle the logic of displaying the total area of ​​the shapes. The SumCalculatorOutputter class will operate as follows:

Now the logic we need to export data to the user will be handled by the SumCalculatorOutputter class.

Open-closed principle (OCP)

OCP states:

Do not modify an existing class, but can extend it

This simply means that a class should be easily expanded without modification. Let’s take a look at the AreaCalculator class, especially the sum method.

If we wanted the sum method to be able to sum the area of ​​more shapes, we would have to add more if / else blocks and that went against the OCP principle.

One way we can improve this sum method is to remove the logic to calculate the area of ​​each shape from the sum method and put it into the class of each specific shape.

We do the same thing with Circle class. Now, the sum of any shape is very simple as follows:

Now we can create another shape class and pass it on when summing without breaking the code. However, now another problem arises, which is how do we know that the object passed to AreaCalculator is really a shape, and if it’s a true shape, then it has a method called area. or not?

The interface is an integral part of SOLID, for example, we will create an interface that all shapes implement:

In our AreaCalculator sum method, we can check if the provided shapes are actually ShapeInterface instances, otherwise we’ll return an exception:

Liskov substitution principle (LSP)

LSP stated:

A child class should be able to replace its parent class without causing an error.

Continue to use the AreaCalculator class, assuming we have the VolumeCalculator class that extends the AreaCalculator class:

In the class SumCalculatorOutputter:

If we try to run the following code:

Our program will run nicely, but when we call the HTML method on the object $ output2, we get an E_NOTICE error that informs us of the error about converting the array to string.

To fix this, instead of returning an array from the sum method of the VolumeCalculator class, we simply need:

Interface segregation principle (ISP)

The ISP stated:

A client should never be forced to implement an interface that it does not use or depends on the methods it does not use.

Still using the shape example, we know that a solid shape exists, and the solid shape also has a “volume” property, so we can add another contract to ShapeInterface as follows. :

Any shape we create will have to implement the volumn method, but we know that squares are 2D and they have no mass, so this interface will force the Square class to implement a method it doesn’t use. use. The ISP says no to this, and instead can create another interface called SolidShapeInterface with a contract that contains a “volume” property and solid shapes, such as a cube, that can implement this interface:

This is a much better approach, but one will have to be especially careful when type-hinting these interfaces instead of using ShapeInterface or SolidShapeInterface. We can create another interface, maybe ManageShapeInterface and implement it on both planes and shapes, this way we can easily see that it has a unique API for managing shapes. For example:

Now in the AreaCalculator class, we can easily replace the area method calls with calculate and also check if the object is an instance of ManageShapeInterface rather than ShapeInterface.

Dependency Inversion principle (DIP)

Last but not least, the DIP principle. DIP statement:

Entities should only depend on abstractions, not specific concretions or implementations. In addition, high level modules should not be dependent on low level modules, but should be dependent on abstractions.

It sounds like a word, but it is really easy to understand. This principle allows decoupling. Examples are the best way to explain this principle:

First, MySQLConnection is a low level module while PasswordReminder is high level, but in the definition of D in SOLID states that “should only depend on abstractions, not concretions”, the snippet above en violates this rule because the PasswordReminder class is forced to depend on the MySQLConnection class.

Later, if you change the database engine, you will also have to modify the PasswordReminder class and thus violate the OCP principle.

The PasswordReminder class should not care what database our application uses. To fix this, we “code into the interface”. Because high-level and low-level modules should depend on abstraction, we can create an interface:

The interface has a connect method and the MySQLConnection class implements this interface, and, instead of the type-hint class MySQLConnection directly in the constructor of PasswordReminder, we type-hint the interface. Now, no matter what type of database we use, the PasswordReminder class can easily connect to the database without any problems and OCP will not be violated.

Following the snippet above, we can now see that both high and low level modules are dependent on abstraction.

Conclude

Actually, SOLID may seem like a pain in the beginning, but with continuous use and adherence to its principles, you will get used to and write code that can be extended, changed, tested, and refactor.

Share the news now

Source : Viblo