Blog

SOLID Principles of Object-Oriented Design

solid-webepower

SOLID is an acronym for the first five object-oriented design (OOD) principle by Robert C. Martin.
These principle helps to make it easy for a programmer to develop software that is easy to maintain and extend. It also makes it easier for developers to avoid code smells, easily refactor code and also support agile development.

What S.O.L.I.D is?

S – Single-responsibility Principle
O – Open Closed Principle
L – Liskov substitution Principle
I – Interface segregation Principle
D – Dependency Inversion Principle

Let’s understand each principle individually to understand why S.O.L.I.D can help make us better developers.

1. Single-responsibility Principle

It stated as “A class should have one and only one reason to change, meaning that a class should have only one job to do”

Let’s understand this with an example.

Imagine that we have an interface for users like this:

interface UserInterface
{
    public function setId($id);
    public function getId();
    public function setName($name);
    public function findById($id);
    public function create();
    public function insert();
    public function update();
    public function delete();
}

In this case, its clear to see the CRUD methods should be placed in the data access layer insulated from the accessors. This implies that there are two overlapped responsibilities coexisting here which makes the class change for different requirements.

 

So we can solve this problem by creating two different interfaces, UserInterface and
UserMapperInterface

interface UserMapperInterface
{
    public function findById($id);
    public function create();
    public function insert(UserInterface $user);
    public function update(UserInterface $user);
    public function delete($id);
}

interface UserInterface
{
    public function setId($id);
    public function getId();
    public function setName($name);
    public function setEmail($email);
    public function getEmail();
}

2. Open-Closed Principle

It stated that” Objects or entities should be open for extension, but closed for modification”.

It means that a class should be easily extendable without modifying the class itself.

Let’s understand this and assume that we have a Board class that contains Rectangles and calculate the area of the Rectangles.

class Rectangle
{
    public $width;
    public $height;
}
class Board
{
    public $rectangles[];
    public function calculateArea() {
        $area = 0;
        foreach ($this->rectangles as $rectangle) {
            $area += $rectangle->width * $rectangle->height;
        }
    }
}

But now if you the new requirements which needs to add circles to the Board.

We have to make our Board class know about Rectangles and Circles, but if we would follow OCP we should not need to touch Board or Rectangle. We should reuse the existing Board and apply it to the circle.

interface Shape {
    public function area();
}

class Rectangle implements Shape 
{
    public function area() {
        return $this->width * $this->height;
    }
}

class Circle implements Shape 
{
    public function area() {
        return $this->radius * $this->radius * pi();
    }
}

class Board
{
    public function calculateArea() {
        $area = 0;
        foreach ($this->shapes as $shape) {
            $area+= $shape->area();
        }
    }
}

3. Liskove Substitution Principle

It stated that “Let q(x) be property about objects of x of type T. Then q(y) should be provable for objects y of type S where S is a subtype of T”.

It means that every subclass/derived class should be substitutable for their base/parent class.

Let’s understand with an example. In this case, we have our class Rectangle and now we want to create a class Square that extends from Rectangle.

class Rectangle
{
    public function setWidth($w) { $this->width = $w; }
    public function setHeight($h) { $this->height = $h; }
    public function getArea() { return $this->height * $this->width; }
}

class Square extends Rectangle
{
    public function setWidth($w) { $this->width = $w; $this->height = $w; }
    public function setHeight($h) { $this->height = $h; $this->width = $h; 
}

Now we can create our function to calculate classes.

function areaOfRectangle() {
    $rectangle = new Rectangle();
    $r->setWidth(7); $r->setHeight(3);
    $r->getArea(); // 21
}

As the LSP says, we should be able to change Rectangle by Square:

function areaOfRectangle() {
    $rectangle = new Square();
    $r->setWidth(7); $r->setHeight(3);
    $r->getArea(); // 9
}

As we said “we must make sure that Square classes are extending the Rectangle without changing their behaviour”. But as we are receiving the output 21 which is not equal to 9

The solution would be to manage the class inheritance hierarchies correctly, for example by introducing the interface Quard.

interface Quard
{
    public function setHeight($h);
    public function setWidht($w);
    public function getArea();
}
class Rectangle implements Quard;

class Square implements Quard;

4. Interface Segregation Principle

It stated as “A class can implement multiple interfaces simultaneously, We shouldn’t force clients to deploy methods unnecessary”.

Let’s understand this and think about digital agency that has workers, so I create the interface Worker.

interface Worker {
    public function takeBreak()
    public function code()
    public function callToClient()
    public function attendMeetings()
    public function getPaid()
}

Let’s assume we have Manager class which implements Worker interface and Tester we are going to have problems with unused methods.

class Manager implements Worker
{
    public function code() { return false; }
}

class Developer implements Worker
{
    public function callToClient() { echo $swear_word; }
}

Now we should create more interfaces.

interface Worker
{
    public function takeBreak()
    public function getPaid()
}

interface Coder {
    public function code()
}

interface ClientFacer {
    public function callToClient()
    public function attendMeetings()
}

class Tester implements Worker, Coder {}

class Manager implements Worker, ClientFacer {}

5. Dependency Inversion Principle

It stated that “Entities must depend on abstraction, not on concretions. It states that the high-level module must not depend on the abstraction”

Let’s understand this principle and we have Worker class, which is the high level and then a class called Worker. The Manager can make Worker to work.

class Worker
{
    public function work() {}
}

class Manager
{
    private $worker;
    public function setWorker($w) {
        $this->worker = $w;
    }

    public function manage() {
        $this->worker->work();
    }
}

Now we need to add a new kind of specialized workers, we create a new class SpecializedWorker for this

class SpecializedWorker
{
    public function work() {}
}

With this we are facing problems such as modify the ManagerClass, and some of the functionality of the Manager might be affected. Now we follow the Dependency Inversion Principle and now implement the previous example.

interface Employee
{
    public function work();
}

class Worker implements Employee
{
    public function work() {}
}
class SpecializedWorker implements Employee
{
    public function work() {}
}

Conclusion

Using the S.O.L.I.D principles implies an increased effort. It will result in more classes and interfaces to maintain. While designing a class the principle of S.O.L.I.D are guidelines that can be applied to remove code smells.

If you have any doubt or query feel free to contact us or drop your query we will get back to you soon. We want to hear from you.