Solid Principles in PHP – Interface Segregation

The letter I in SOLID stands for the interface segregation principle. The principle states that a client should not be forced to implement an interface that it doesn’t use. What it comes down to is the knowledge, the knowledge that one object has over another object. Let’s demonstrate this for you with an example.

But first, this example and all the examples that I have touched upon in the rpevious principles were made by Jeffery Way from Laracasts. If you haven’t already, I recommend signing up to his service – screencasts for the modern developer. It will be well worth your while.

Let’s take an example from star trek. We have a Captain class and the responsibility of the Captain class is to manage workers.

class Captain
{
    public function manage(Worker $worker)
    {

    }
}

Now we’ll setup our worker class, where the worker can work and sleep:

class Worker 
{
    public function work()
    {

    }

    public function sleep()
    {

    }
}

We now can execute the functions for the worker class:

class Captain
{
    public function manage(Worker $worker)
    {
        $worker->work();
        $worker->sleep();
    }
}

Let’s say now we want to add an Android class. You know that an android does not sleep and you now going to code to an interface:

interface WorkerInterface
{
    public function work();
    public function sleep();
}

// change class Worker to HumanWorker
class HumanWorker implements WorkerInterface
{
    public function work()
    {
        return "Human Working";
    }

    public function sleep()
    {
        return "Human Sleeping";
    }
}

Let’s setup AndroidWorker which will also implement WorkerInterface:

class AndroidWorker implements WorkerInterface
{
    public function work()
    {
        return "Android Working";
    }

    public function sleep()
    {
        return null;
    }
}

As we know, an android does not sleep, so there is no need for the sleep method. We can’t just remove the method because the AndroidWorker has a binding contract with the WorkerInterface that it has to implement every method in the contract. So you’ll most likely set the return value for the sleep method to null. Doing this violates the Interface Segregation principle.

As state previously, a client should not be forced to implement an interface that it doesn’t use. We are forcing AndroidWroker to use the sleep method, even though it doesn’t require it. How can we improve the code. One way is to divide it into smaller chunks. An interface that contains a single method is fine. So we’ll setup our interfaces as follows:

interface WorkableInterface
{
    public function work();
}

interface SleepableInterface
{
    public function sleep();
}

So HumanWorker will implement the WorkableInterface and SleepableInterface and the AndroidWorker will only implement WorkableInterface.

class HumanWorker implements WorkableInterface, SleepableInterface
{
    public function work()
    {
        return "Human Working";
    }

    public function sleep()
    {
        return "Human Sleeping";
    }
}

class AndroidWorker implements WorkableInterface
{
    public function work()
    {
        return "Android Working";
    }
}

Let’s go back to our captain class. How does the Captain know which which worker to execute, is it android or is it human. Do we use an if statement to check what type of worker is being passed through and execute accordingly. No we don’t, doing this vioolates the open-closed principle.

What we do is create another interface called ManagableInterface which whill contain one function beManaged.

interface ManagableInterface
{
    public function beManaged();
}

Both AndroidWOrker and HumanWorker will implement ManagableInterface:

class HumanWorker implements WorkableInterface, SleepableInterface, ManagableInterface
{
    public function work()
    {
        return "Human Working";
    }

    public function sleep()
    {
        return "Human Sleeping";
    }

    public function beManaged()
    {
        $this->work();
        $this->sleep();
    }
}

class AndroidWorker implements WorkableInterface, ManagableInterface
{
    public function work()
    {
        return "Android Working";
    }

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

Now if we come back to Captain class, rather than calling the specific methods where it may need to have an understanding of the type of object being referenced. We don’t want to do that. We want to do the following:

class Captain
{
    public function manage(ManagableInterface $worker)
    {
        $worker->beManaged();
    }
}

The most important thing to understand though is now because the manage method does not depend upon the worker itself, we have improved the deisign and reduced coupling.

Leave a Reply

Your email address will not be published.