This is that last part of a five part series about SOLID class design principles by Robert C. Martin. The SOLID principles focus on achieving code that is maintainable, robust, and reusable. In this post, I will discuss the Interface Segregation Principle.

The Interface Segregation Principle (ISP): Clients should not be forced to depend upon interfaces that they do not use.

The Interface Segration Principle (ISP) is best demonstrated with an example, so let's dive right in.

//EXAMPLE 1
class SomeButton {
private:
    SomeController* _controller;
public:
    void setController(SomeController* controller);
};

class SomeWindow {
private:
    SomeController* _controller;
public:
    void setController(SomeController* controller);
};

class SomeController {
private:
    SomeWindow* _window;
    SomeButton* _okButton;
    SomeButton* _cancelButton;
public:
    void onButtonDown(SomeButton* button);
    void onButtonUp(SomeButton* button);
    void onWindowOpen(SomeWindow* window);
    void onWindowClose(SomeWindow* window);
    void onWindowMoved(SomeWindow* window);
};

In the above example, there is SomeController which handles clicks from two SomeButton objects and window events from a SomeWindow object. The problem with this design is that SomeButton and SomeWindow both have a SomeController pointer. SomeButton does need to call the onButton[X] methods of the controller object, but it also has access to the onWindow[X] methods which are useless to the button. The presence of useless onWindow[X] methods is a violation of the ISP. There is also a cyclic dependency, which is another hint that something is amiss.

To conform to the ISP, SomeButton must not have access to the onWindow[X] methods, and SomeWindow must not have access to the onButton[X] methods. This can easily be done by applying the Dependency Inversion Principle, and the Open Closed Principle. Here is one way to improve the design:

//EXAMPLE 2
// The Button ///////////////////////////////////////////////////////

class SomeButtonController {
public:
    virtual void onButtonDown(SomeButton* button) = 0;
    virtual void onButtonUp(SomeButton* button) = 0;
};

class SomeButton {
private:
    SomeButtonController* _controller;
public:
    void setController(SomeButtonController* controller);
};

// The Window ///////////////////////////////////////////////////////

class SomeWindowController {
public:
    virtual void onWindowOpen(SomeWindow* window) = 0;
    virtual void onWindowClose(SomeWindow* window) = 0;
    virtual void onWindowMoved(SomeWindow* window) = 0;
};

class SomeWindow {
private:
    SomeWindowController* _controller;
public:
    void setController(SomeWindowController* controller);
};

// The Controller ///////////////////////////////////////////////////////

class SomeController : public SomeButtonController, public SomeWindowController {
private:
    SomeWindow* _window;
    SomeButton* _okButton;
    SomeButton* _cancelButton;
public:
    void onButtonDown(SomeButton* button);
    void onButtonUp(SomeButton* button);
    void onWindowOpen(SomeWindow* window);
    void onWindowClose(SomeWindow* window);
    void onWindowMoved(SomeWindow* window);
};

The improved design above uses abstract base classes and (the good kind of) multiple inheritance. SomeButton now only has access to button related controller methods, and SomeWindow only has access to window related controller methods, yet SomeController objects can be plugged into both.

Why bother adhering to the ISP?

Martin Fowler mentions the cost of recompiling as a reason to adhere to the ISP. In Example 1, if SomeController were to change, then both SomeButton and SomeWindow would need to be recompiled. In Example 2, this is not a problem. I don't think that's a huge deal, because a fast compile time is the least of your worries when you're writing software. (2014 update: after compiling Boost a few times, one grows to appreciate fast build times. If you're the victim of a monstrous, Boost-like codebase, then this could be important to you.)

Martin also mentions that "fat interfaces" — interfaces with additional useless methods — lead to inadvertent coupling between classes. This is the real reason why the SIP should be adhered to.

Coupling is the bane of reusability. In Example 1, SomeButton and SomeWindow are not reusable, and can only be used in one window of the application. It's absurd to require different window and button classes for every window in the application.

Fat interfaces also introduce unnecessary complexity, which isn't good for maintainability or robustness. It's very clear in Example 2 that SomeButton will only call the two methods on SomeButtonController. It's nice and simple. However, the distinction is not so clear in Example 1. In reality SomeController would have many more than five methods, and SomeButton could be using any number of them in weird and wonderful ways. I imagine that dependency hell is full of developers running around saying "this SomeButton closes the window, so I'll just call onWindowClose instead of onButtonDown." I know that I would be wailing and gnashing my teeth.

ISP, OCP, and DIP... OMG WTF BBQ?

The design flaw in Example 1 doesn't just violate the ISP, it also violates the DIP and OCP. This is quite common, so if you're adhering to the DIP and OCP properly then you won't come across many ISP violations. Having said that, the ISP does gives you another handy way to evaluate your class design. It teaches you to ask yourself "do I need all the methods on this interface I'm using?" If the answer is no, then you might want to use a different interface and apply some of the other SOLID principles.