Open-Closed Principle in Object-Oriented Design (SOLID)

Author
By Darío Rivera
Posted On in SOLID

In a previous post we saw an introduction to the SOLID Principles in a purely theoretical way. Today we'll see everything about the second SOLID principle on the list, The Open-Closed Principle. Let's start by giving a definition of this principle.

Definition

This principle states that entities (classes, methods or functions) should be open for extension but closed for modification. That an entity is open for extension, means that it is possible to change the behavior of that entity. On the other hand, being closed for modification, implies that it should be possible to change the behavior without modifying the original source code.

In other words, it must be possible to change the behavior of a class without modifying its source code. But isn't that contradictory? How can this be possible in practical terms? That is what we are going to see in the following example.

Example

Let's imagine a very simple but practical example in PHP.

class MercedesBenz
{
    public $speed = 0;

    protected $model = '2019';
}
class Pagani
{
    public $speed = 0;

    protected $model = '2020';
}

As you can see, we have defined two classes called MercedesBenz and Pagani. Now comes the interesting part, let's suppose that we want to implement the acceleration functionality in those classes. The acceleration functionality could be used by a driver. So for now, let's make that implementation depend on each car.

class Driver
{
    public function accelerate(Auto $auto)
    {
        if ($auto instanceof MercedezBenz)
        {
            $auto->speed += 10;
        } 
        elseif ($auto instanceof Pagani)
        {
            $auto->speed += 15;
        }
    }
}

With this, we have programmed the driver to accelerate each car at a rate of 10 Km/h and 15 Km/h, depending on whether it is a Mercedez Benz or Pagani, respectively. However, note that every time a new car is added, that is, a new class is created, the accelerate function of Driver must also be modified to support the acceleration functionality. That is, the Driver class is open to modification, so we are violating the OCP principle. The solution to this has two sides which we will see below.

Dynamic Polymorphism

The first technique consists of separating the extensible behavior behind an interface and eliminating dependencies. Let's then create that interface and implement it in each class.

interface AutoInterface
{
    public function speedUp();
}
class MercedesBenz implements AutoInterface
{
    public $speed = 0;

    protected $model = '2019';

    public function speedUp()
    {
        $this->speed += 10;
    }
}
class Pagani implements AutoInterface
{
    public $speed = 0;

    protected $model = '2020';

    public function speedUp()
    {
        $this->speed += 15;
    }
}

Finally, let's move the dependencies of the driver class. In this way, the driver entity or class only needs to execute the method that it knows from the interface, that is, the speedUp() method. Note that this removes the dependencies of the Driver class with each specific car class, thus reducing coupling and achieving compliance with the OCP principle since the Driver class does not need to be modified (closed to modification) every time a new car class is created (extends).

class Driver
{
    public function accelerate(AutoInterface $auto)
    {
        $auto->speedUp();
    }
}

Static Polymorphism

The second technique consists of creating an abstract or template class such that the child classes must define that behavior.

abstract class Auto
{
    public $speed = 0;

    abstract public function speedUp();
}
class MercedesBenz extends Auto
{
    public $speed = 0;

    protected $model = '2019';

    public function speedUp()
    {
        $this->speed += 10;
    }
}
class Pagani extends Auto
{
    public $speed = 0;

    protected $model = '2020';

    public function speedUp()
    {
        $this->speed += 15;
    }
}

For the client class, it is indifferent. However, it is noteworthy that now the parameter type is not an interface but an abstract class.

class Driver
{
    public function accelerate(Auto $auto)
    {
        $auto->speedUp();
    }
}

Acerca de Darío Rivera

Author

Application Architect at Elentra Corp . Quality developer and passionate learner with 10+ years of experience in web technologies. Creator of EasyHttp , an standard way to consume HTTP Clients.

LinkedIn Twitter Instagram

Sólo aquellos que han alcanzado el éxito saben que siempre estuvo a un paso del momento en que pensaron renunciar.