Skip to main content

SOLID: Interface Segregation Principle

About 5 years ago, I wrote a series of short articles on the SOLID principles, shared by email to my colleagues as a weekly, Monday morning read. They were then archived for reference to our internal knowledge base. That's getting a revamp and re-platforming now, and given their age, I'm not sure these pieces will be making the cut. Given it's fairly timeless stuff, seems a shame for them to disappear into the virtual ether, so I'll save them for posterity here.

The SOLID Principles

The SOLID principles are a well established set of tenets intended to guide software design toward a maintainable solution. They are widely applicable and operate at a high level, so are well worth considering for almost any type of application we are looking to build, using any framework or language.

For the full set of articles in the series, see:

I is for Interface Segregation Principle

The interface segregation principle (ISP) which states that clients should not be forced to implement interfaces they don't use. What this is driving towards is the creation of several small interfaces in preference to a single large one.

If you have a situation where you are having to implement a large interface that contains methods that don't apply in your situation, then you have no choice but to implement those methods anyway (and probably throw NotImplementedExceptions). This isn't ideal from a code readability point of view - to the user of your class there's no indication that some methods are callable and some aren't. It also creates an additional barrier to anyone looking to create alternate implementations of the interface.

To illustrate this, let's assume we have a component for sending emails that implements an interface looking something like this:

public interface IMessage
{
    IList ToAddresses { get; set; }
    IList CCAddresses { get; set; }
    string Body { get; set; }
    string Subject { get; set; }

    void Send();
}

We are then asked to support sending messages by SMS. Initially we might think to use the same interface and create an alternate implementation that uses a service like Twilio to send the messages. But for an SMS, the interface isn't ideal - text messages don't have a subject, nor the concept of CC addresses.

By implementing the interface as is, we would expose these properties to users of the class and have to throw exceptions or simply ignore the values if they were set. Better would be to break out the interfaces to something like the following:

public interface IMessage
{
    IList ToAddresses { get; set; }
    string Body { get; set; }
    string Subject { get; set; }

    void Send();
}

public interface IEmailMessage : IMessage
{
    IList CCAddresses { get; set; }
    string Subject { get; set; }
}

It's worth noting that the point here isn't really to try to predict the future - that we might need an alternate messaging method - but rather that it's better to take the time to refactor to a better suited interface, adhering to the ISP, at the time we know about the new requirement.

For a real-world example at my company, this principle was considered in the creation of the "Big Garden Birdwatch" functionality for RSPB. This feature had a number of forms and processes, with similar requirements but subtle variations, so a number of steps were taken to avoid repeating code using inheritence chains for controllers, view models and factories.

Specifically functionality included forms that:

  • Are used for collecting user details that need to be retained in the session
  • Are used for collecting the answers to questionnaires that are recorded in a database
  • And in particular, not all forms needed both these features - some needed one, some the other and some both.

To support that two separate interfaces were created for the view models..

The controllers for these features were arranged in an inheritence chain, with a base controller providing a method like this:

protected void UpdateProfileWithAnswersToCustomQuestions(IUserJourneyProfile model,
    ICustomQuestions postedModel)
{
}

Because this method depends only on the object passed to it having the properties and methods defined in ICustomQuestions, it can accept any view model that implements this interface. We don't have to have variations of the method dealing with all the different concrete view models classes.

Comments