Basics-Wrapup

View on GitHub

SOLID

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

SOLID stands for


Single-responsiblity Principle

A class should have one and only one reason to change, meaning that a class should have only one job.

even it’s job is a method or a property of a defined class

code example:

class Square 
{
    public $length;

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

class Circle
{
    public $radius;

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

class AreaCalculator
{
    protected $shapes;

    public function __construct($shapes = [])
    {
        $this->shapes = $shapes;
    }

    public function sum()
    {
        foreach ($this->shapes as $shape) {
            if (is_a($shape, 'Square')) {
                $area[] = pow($shape->length, 2);
            } elseif (is_a($shape, 'Circle')) {
                $area[] = pi() * pow($shape->radius, 2);
            }
        }

        return array_sum($area);
    }

    public function output()
    {
        return implode('', [
          '',
              'Sum of the areas of provided shapes: ',
              $this->sum(),
          '',
      ]);
    }
}

$shapes = [
  new Square(5)
];

$areas = new AreaCalculator($shapes);

echo $areas->output();

also to reduce using many classes for our users we can make use of inheritance to make each shape inherite methods from base class such as shapeProperty class such that

class shapeProperties {

    public static function whoAmI() {
        return get_called_class();
    }
    
    public function Area(){
        if (is_a($this, 'Square')) {
            $area = pow($this->length, 2);
            return $area;
            //return "Square";
        } elseif (is_a($this, 'Circle')) {
            $area = pi() * pow($this->radius, 2);
            //return "Circle";
            return $area;
        }
    }
    //...
    //use as many methods as you want the circle and square classes still sees them and can also override them
}
//... Square class extends shape
//... Circle class extends shape
$circle1 = new Circle(5);
echo "\nwho is this area ".$circle1->Area(); //this will give us the previous result

also if we want to print output in different formats this will violate this principle so to solve this we create a new class and remove output method inside AreaCalculator class such that

class SumCalculatorOutputter
{
    protected $calculator;

    public function __constructor(AreaCalculator $calculator)
    {
        $this->calculator = $calculator;
    }

    public function JSON()
    {
        $data = [
          'sum' => $this->calculator->sum(),
      ];

        return json_encode($data);
    }

    public function HTML()
    {
        return implode('', [
          '',
              'Sum of the areas of provided shapes: ',
              $this->calculator->sum(),
          '',
      ]);
    }
}

and for our second approach we can implement those methods inside shapeProperty class

class shapeProperties {
    ...
    public function JSON()
    {
        ...
    }
    public function HTML()
    {
        ...
    }
}

Open-closed Principle

Objects or entities should be open for extension but closed for modification.

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

Let’s revisit the AreaCalculator class and focus on the sum method which returns the sum of shapes inputed to AreaCalculator

Consider a scenario where the user would like the sum of additional shapes like triangles, pentagons, hexagons, etc. You would have to constantly edit this file and add more if/else blocks. That would violate the open-closed principle.

A way you can make this sum method better is to remove the logic to calculate the area of each shape out of the AreaCalculator class method and attach it to each shape’s class.

Here is the area method defined in Square & Circle:

class Square
{
    public $length;

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

    public function area()
    {
        return pow($this->length, 2);
    }
}
class Circle
{
    public $radius;

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

    public function area()
    {
        return pi() * pow($shape->radius, 2);
    }
}

also The sum method for AreaCalculator can then be rewritten as:

class AreaCalculator
{
    // ...

    public function sum()
    {
        foreach ($this->shapes as $shape) {
            $area[] = $shape->area();
        }

        return array_sum($area);
    }
}

now for each new shape we create we need to define it’s area method

However, another problem arises. How do you know that the object passed into the AreaCalculator is actually a shape or if the shape has a method named area?

we can solve this by interfaces such that

interface ShapeInterface
{
    public function area();
}

and modify your shapes’ classes to

class ... implements ShapeInterface

and In the sum method for AreaCalculator, you can check if the shapes provided are actually instances of the ShapeInterface; otherwise, throw an exception such that

class AreaCalculator
{
    // ...
    public function sum()
    {
        foreach ($this->shapes as $shape) {
            if (is_a($shape, 'ShapeInterface')) {
                $area[] = $shape->area();
                continue;
            }

            throw new AreaCalculatorInvalidShapeException();
        }
        return array_sum($area);
    }
}

the same idea can be applied with base class that have area method and shapes extends from it so if they are having this class then they have or implements this method but interfaces are more clear and does the job pretty well.


Liskov Substitution Principle

Let q(x) be a property provable 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.

this pattern states that if an object B inherits from object A then all properties of type A is provable for objects of type B or can be handled without any problem

for substituting talk(animal_obj) - animal_obj is an object of type animal and talk is a method can be applied to that object
talk(bird_obj) - bird_obj is an object of type bird and talk method can be applied to it where bird is a subtype of (inherits from) animal type

it seems obvious that we didn’t need to explain that but This means that every subclass(child) or derived class should be substitutable(replacable) for their base or parent class.which means child class have the parent method and properties

Building off the example AreaCalculator class, consider a new VolumeCalculator class that extends the AreaCalculator class:

class VolumeCalculator extends AreaCalculator
{
    public function construct($shapes = [])
    {
        parent::construct($shapes);
    }

    public function sum()
    {
        // logic to calculate the volumes and then return an array of output
        return [$summedData];
    }
}

remember the method of SumCalculatorOutputter which can be applied to both area and volume classes which has sum method (can be inserted or implements this as interface) and each class implements it should has it

so for conclusion on our example

VolumeCalculator is subcalss of AreaCalculator then property SumCalculatorOutputter should be fine for both of them as they both have the same method sum but VolumeCalculator overrides it with it’s implementation


Interface Segregation Principle

A client should never be forced to implement an interface that it doesn’t use, 
or clients shouldn’t be forced to depend on methods they do not use.

this mean you need to separate interfaces and only implement the one you need or will depend on later.

interface ShapeInterface
{
    public function area();
}

interface ThreeDimensionalShapeInterface
{
    public function volume();
}
class Cuboid implements ShapeInterface, ThreeDimensionalShapeInterface
{....

and only implements the one you need or depend on. also, for type-hinting or use your interface method inside another interface method you can do it such that

interface ManageShapeInterface
{
    public function calculate();
}
class Square implements ShapeInterface, ManageShapeInterface
{...
public function calculate()
    {
        //do logic
        return $this->area();
    }

}
class Cuboid implements ShapeInterface, ThreeDimensionalShapeInterface, ManageShapeInterface
{...
public function calculate()
    {
        //do different login but both are depend on area method inside ShapeInterface
        return $this->area();
    }
}

Now in AreaCalculator class, you can replace the call to the area method with calculate and also check if the object is an instance of the ManageShapeInterface and not the ShapeInterface.

that mean you can nest methods from different interfaces inside each other but you should create different interface for each new method and make sure you implement both the base interface and the derived interface

That satisfies the interface segregation principle.


Dependency Inversion Principle

Entities must depend on abstractions, not on concretions. It states that the high-level module 
must not depend on the low-level module, but they should depend on abstractions.

This principle allows for decoupling and make layers independent.

Here is an example of a PasswordReminder that connects to a MySQL database:

class MySQLConnection
{
    public function connect()
    {
        // handle the database connection
        return 'Database connection';
    }
}

class PasswordReminder
{
    private $dbConnection;

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

First, the MySQLConnection is the low-level module while the PasswordReminder is high level, PasswordReminder class is being forced to depend on the MySQLConnection class this would violate the Dependency Inversion Principle.

Later, if you were to change the database engine, you would also have to edit the PasswordReminder class, and this would violate the open-close principle.

The PasswordReminder class should not care what database your application uses. To address these issues, you can code to an interface since high-level and low-level modules should depend on abstraction:

interface DBConnectionInterface
{
    public function connect();
}

The interface has a connect method and the MySQLConnection class implements this interface. Also, instead of directly type-hinting MySQLConnection class in the constructor of the PasswordReminder, you instead type-hint the DBConnectionInterface and no matter the type of database your application uses, the PasswordReminder class can connect to the database without any problems and open-close principle is not violated.

class MySQLConnection implements DBConnectionInterface
{
    public function connect()
    {
        // handle the database connection
        return 'Database connection';
    }
}

class PasswordReminder
{
    private $dbConnection;

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

This code establishes that both the high-level and low-level modules depend on abstraction.