PHP Course : SOLID - Open-Closed

profile picture

PHP Course : SOLID - Open-Closed

The second principle of SOLID is called Open-Closed, and helps developer to mature well designed code.

This is a confusing one, but I'll try to make it easy to grasp.

Definition

First of all, this principle states that entities should be open for extension, but closed for modification.

"Entities" are basically a class or a function.

- Okay thanks for the definition but it doesn't really make any sense, I don't even know what to do with it

Let me try to simplify this at first

Monkey Explanation

When we say "open for extension", we are saying that it should be simple to change the behavior of a specific entity (a class, a method, a function).

When we talk about "closed for modification", we mean "changing the behavior without modifying the source code". This is why the term "extension" is important.

Now of course this principle is a goal to aim. It is very difficult to follow it perfectly.

In conclusion, the goal is to be able to change the behavior of a class without touching his code source, using an "extension" approach.

Example

Let's imagine a Square class that has a height and width property.

class Square
{
    public $width;
    public $height;

    public function __construct($height, $width)
    {
        $this->height = $height;
        $this->width = $width;
    }
}

Now we want a way to calculate the surface area of that Square. Following the Single Responsability Principle, we will create a new class dedicated the calculating the area of a Shape. Let's do that.

class AreaCalculator
{
    public function calculate($squares)
    {
        foreach ($squares as $square) {
            $area[] += $square->width * $square->height;
        }

        return array_sum($area);
    }
}

Okay, everything works fine and we follow SRP.

But then one day, you need to also calculate the area of a Circle. So what do you do ? You create a Circle class, sure:

class Circle
{
    public $radius;

    public function __construct($radius)
    {
        $this->radius = $radius;
    }
}

Sure that's fine, but in order to calculate the area of Circles, you'll need to heavily modify the AreaCalculator class. Not good, we just broke the Open Close Principle.

Take a look at the AreaCalculator class. The variables are called $squares, but we can easily change that to a generic name like $shapes.

Now inside your foreach, how do you proceed? The quick and dirty way is to do a quick check like:

foreach ($shapes as $shape) {
    if ($shape instanceof Circle) {
        $area[] += $shape->radius * $shape->radius * pi();
    } else {
        $area[] += $shape->width * $shape->height;
    }
}

This is bad. This if & else is a dead ringer that you broke the Open Closed Principle.

We just modified AreaCalculator class, meaning it was open for modification, but it should have been closed for modification.

Imagine after that you now need to support Triangle shapes, and then dozen other types of shapes. You'll get a very big if/else imbrication.

Solution

When you have a module that you want to extend or modify (that's what we want to do), separate the extensible behavior behind an interface, and flip the dependencies.

In our case, we see that each type of a shape has its own surface calculation method.

Square, Circle, Triangle and all these classes are indeed Shapes. That means we can define an interface here!

interface ShapeInterface
{
    public function area();
}

That's really all we need. A Shape must simply implements an area method to calculate its surface area.

Then of course you need to implement this interface and the method.

class Square implements ShapeInterface
{
    public $width;
    public $height;

    public function __construct($height, $width)
    {
        $this->height = $height;
        $this->width = $width;
    }

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

class Circle implements ShapeInterface
{
    public $radius;

    public function __construct($radius)
    {
        $this->radius = $radius;
    }

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

You should begin to see where we are going here ๐Ÿ˜„

In your Calculator class, it's going to be a lot easier.

class AreaCalculator
{
    public function calculate($shapes)
    {
        foreach ($shapes as $shape) {
            $area[] += $shape->area();
        }

        return array_sum($area);
    }
}

Conclusion

Using the demonstrated approach, when we need to extend our application by providing a new kind of Shape, we will be able to do just that and actually never even touch AreaCalculator again. The class is Open-Closed. Open for extension. Closed for modification.

You also see that this principle works really well with the Single Responsability Principle. SOLID is designed to work all together, and the principles share many similarities.

php
lesson
php course
solid
open-closed
principle

2019 My Dynamic Production SPRL All rights Reserved.