Interface Segregation Principle

Interface Segregation Principle

The Interface Segregation Principle is the next stop on our tour of the 5 solid principles. Thankfully, it’s a pretty easy one to understand. All it means is that a client should not be forced to implement an interface that it will never use. This is the main idea of the Interface Segregation Principle. Just like the rest of the five solid design patterns, the Interface Segregation Principle exists to help decouple modules within the software and make the code easier to understand, refactor, and maintain.

Interface Design

At this point, we know how to use an interface in our programs. This is great, but we can still run into problems with interfaces as well. Namely, with the Interface Segregation Principle, we want to be sure that we don’t stuff our interfaces with the kitchen sink of methods. If we add too many methods to our interfaces, they become too fat, and we start to lose some of the benefits of solid that we are trying to achieve. Like always, let us take a look at some code to see what we mean.

<?php 

class Underling {
    public function program()
    {
        return 'Program initech systems to deposit fractions of pennies to private account';
    }
    public function filetps()
    {
        return 'Place cover sheet on TPS report before going out';
    }
    public function collate()
    {
        return 'Collect and combine texts, information, and figures in proper order.';
    }
}

class Lumbergh
{
    public function harass(Underling $underling)
    {
        $underling->program();
        $underling->filetps();
        $underling->collate();
    }
}

$lumbergh = new Lumbergh;
$lumbergh->harass(new Underling);

To begin, we have a Lumbergh class which has a harass method that depends on an Underling to function. We can see that an Underling should be able to program, filetps, and collate. So far so good we think. Lumbergh is able to sufficiently harass any new underlings that come across his path. Now what happens when Lumbergh needs to bring in some consultants to Initech. How will that play out? Lumbergh should be able to harass Underlings as well as Consultants with equal impunity. Well, maybe we can set up an interface and make Underlings and Consultants implement that interface so that Lumbergh can harass them. Let’s try it out.

interface UnderlingInterface { public function program(); public function filetps(); public function collate(); } class Underling implements UnderlingInterface { public function program() { return 'Program initech systems to deposit fractions of pennies to private account'; } public function filetps() { return 'Place cover sheet on TPS report before going out'; } public function collate() { return 'Collect and combine texts, information, and figures in proper order.'; } } class Consultant implements UnderlingInterface { public function program() { return 'Outsource task to India'; } public function filetps() { return 'Place cover sheet on TPS report before going out'; } } class Lumbergh { public function harass(Underling $underling) { $underling->program(); $underling->filetps(); $underling->collate(); } } $lumbergh = new Lumbergh; $lumbergh->harass(new Consultant);

We have a small problem. Both our Underling and Consultant classes implement the UnderlingInterface. Consultants however do not collate. The problem is that since a Consultant does in fact implement the UnderlingInterface, and since the UnderlingInterface specifies that we must be able to program, filetps, and collate, then we need to force the Consultant to implement this part of the interface that it will never use. Here is our new updated program, which does work, but it violates the Interface Segregation Principle (Consultant is being forced to implement the collate method).

<?php 

interface UnderlingInterface {
    public function program();
    public function filetps();
    public function collate();
}

class Underling implements UnderlingInterface {
    public function program()
    {
        return 'Program initech systems to deposit fractions of pennies to private account';
    }
    public function filetps()
    {
        return 'Place cover sheet on TPS report before going out';
    }
    public function collate()
    {
        return 'Collect and combine texts, information, and figures in proper order.';
    }
}

class Consultant implements UnderlingInterface {
    public function program()
    {
        return 'Outsource task to India';
    }
    
    public function filetps()
    {
        return 'Place cover sheet on TPS report before going out';
    }
    
    public function collate()
    {
        return null;
    }
}

class Lumbergh
{
    protected $underling;
    
    public function __construct(UnderlingInterface $underling)
    {
        $this->underling = $underling;
    }
    public function harass()
    {
        $this->underling->program();
        $this->underling->filetps();
        $this->underling->collate();
    }
}

$lumbergh = new Lumbergh(new Consultant);
$lumbergh->harass();

How Can We Do Better?

lumbergh
The solution to an ISP problem is to divide the interface into smaller pieces. It is ok in fact to have an interface that has only one method. The problem comes in when you have an interface that just has method after method after method after method that one must implement. The more methods that need to be implemented, the greater the chance for ripple effects in your program.

First off, we can break out our UnderlingInterface into a ProgrammableInterface, FiletpsableInterface, and CollatableInterface.

Not Ideal

interface UnderlingInterface {
    public function program();
    public function filetps();
    public function collate();
}

Better

interface ProgrammableInterface{
    public function program();
}

interface FiletpsableInterface {
    public function filetps();
}

interface CollatableInterface {
    public function collate();
}

Now we can update our Underling and Consultant classes to implement only the interfaces that they need to.

The Final Implementation

We’re pretty close to where we need to be at this point but we need to circle back around to the original Lumbergh class. As it stands now, the Lubergh class is going to need to do type checking on the instance it is passed. It needs to check whether it is dealing with an Underling or a Consultant. The reason for this is that Lumbergh can not call a collate method on a Consultant type. Recall that we are not supposed to have to do this! If we need to do type checking to determine behavior, we are likely breaking something in the solid suite of design principles. This means we need one more interface. We will set this up so that no matter what gets passed into the Lumbergh class, only one behavior will need to take place, and that is to answer_to_Lumbergh(). This is how we do that.

<?php

interface HarassableInterface {
    public function answer_to_Lumbergh();
}

interface UnderlingInterface {
    public function program();
    public function filetps();
    public function collate();
}

interface ProgrammableInterface {
    public function program();
}

interface FiletpsableInterface {
    public function filetps();
}

interface CollatableInterface {
    public function collate();
}

class Underling implements ProgrammableInterface, FiletpsableInterface, CollatableInterface, HarassableInterface  {
    public function program()
    {
        echo 'Program initech systems to deposit fractions of pennies to private account <br>';
    }
    public function filetps()
    {
        echo 'Place cover sheet on TPS report before going out <br>';
    }
    public function collate()
    {
        echo 'Collect and combine texts, information, and figures in proper order <br>';
    }
    
    public function answer_to_Lumbergh()
    {
        $this->program();
        $this->filetps();
        $this->collate();
    }
}

class Consultant implements ProgrammableInterface, FiletpsableInterface, HarassableInterface {
    public function program()
    {
        echo 'Outsource task to India <br>';
    }
    
    public function filetps()
    {
        echo 'Place cover sheet on TPS report before going out <br>';
    }
    
        public function answer_to_Lumbergh()
    {
        $this->program();
        $this->filetps();
    }
}

class Lumbergh
{
    protected $underling;
    
    public function __construct(HarassableInterface $underling)
    {
        $this->underling = $underling;
    }
    public function harass()
    {
        $this->underling->answer_to_Lumbergh();
    }
}

Passing in an Underling gives the desired result

$lumbergh = new Lumbergh(new Underling);
$lumbergh->harass();

// Program initech systems to deposit fractions of pennies to private account
// Place cover sheet on TPS report before going out
// Collect and combine texts, information, and figures in proper order

Passing in a Consultant also gives the desired result

$lumbergh = new Lumbergh(new Consultant);
$lumbergh->harass();

// Outsource task to India
// Place cover sheet on TPS report before going out

Interface Segregation Principle Summary

Don’t force classes to implement behavior or an interface they will never use. It’s that simple!