651.288.7000 info@intertech.com

According to Wikipedia the interface segregation principle (ISP) states that no client should be forced to depend on methods it does not use.  The interface segregation principle was formulated by Robert Martin in the mid 1990s.  For the sake of this post I will use the term “server” to describe a class in an API – a business object for example.  “Client” will be used to describe the consumer of a service – it could be a user interface component for example.  The client has a dependency on a server at some level, the server knows nothing about a client.

As an example let’s assume a team responsible for creating the server was tasked with creating a basic email object.  Being good programmers they designed to an interface and came up with the following Interface and implementation.

    public interface IMessage
    {
        IList<String> ToAddresses { get; set; }
        string MessageBody { get; set; }
        string Subject { get; set; }
        bool Send();
    }

    public class SmtpMessage:IMessage
    {
        public IList<String> ToAddresses { get; set; }
        public string MessageBody { get; set; }
        public string Subject { get; set; }

        public bool Send()
        {
            //Do the real work here
            return true;
        }
    }

Client Team A, responsible for creating an application that sends an email reminder as a part of their process, creates a client that consumes the server and everything works.  Now assume the server team gets a new requirement, from Client Team B, and now they need to support sending a text message.  That’s almost the same as sending an email message so they decide to be polymorphic and use the IMessage interface.  Their new class looks like this: (can you spot the Liskov Substitution Principle violation?)

    public class SmsMessage : IMessage
    {
        public IList<String> ToAddresses { get; set; }
        public string MessageBody { get; set; }
        public bool Send()
        {
            //Do the real work here
            return true;
        }
        public string Subject
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    }

Client Team B is happy, things work great and Client Team A is happy because now they can send SMS messages if they want to.  Client Team A wants to enhance their system to allow for a list of addresses to be included as a blind copy so they give the Server team this new requirement.  The server team comes up with the following change to the interface and new implementations:

    public interface IMessage
    {
        IList<String> ToAddresses { get; set; }
        string MessageBody { get; set; }
        string Subject { get; set; }
        bool Send();
        IList<String> BccAddresses { get; set; }
    }

    public class SmtpMessage:IMessage
    {
        public IList<String> ToAddresses { get; set; }
        public IList<String> BccAddresses { get; set; }
        public string MessageBody { get; set; }
        public string Subject { get; set; }

        public bool Send()
        {
            //Do the real work here
            return true;
        }
    }

    public class SmsMessage : IMessage
    {
        public IList<String> ToAddresses { get; set; }
        public string MessageBody { get; set; }
        public bool Send()
        {
            //Do the real work here
            return true;
        }
        public string Subject
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
        public IList<string> BccAddreses
        {
            get
            {
                throw new NotImplementedException();
            }
            set
            {
                throw new NotImplementedException();
            }
        }
    }

Client Team A gets the new server and things are great but Client Team B is now broken and must recompile and go through a round of regression testing because a feature was added for another team.

The main goal of the interface segregation principle is to loosen the coupling between servers and their clients.  An SMS message supports a list of addresses, a message body, and the notion of sending but Client B was unnecessarily also coupled to the Subject and eventually the BccAddressList property.  In short a change to Any unused property or method or a change to an overly broad interface itself will cause Team B problems.  Let’s rework things a little bit ands see if we can improve the design.

   public interface IMessage
    {
        IList<String> ToAddresses { get; set; }
        string MessageBody { get; set; }
        bool Send();
    }

    public interface IEmailMessage:IMessage
    {
        string Subject { get; set; }
        IList<String> BccAddresses { get; set; }
    }

    public class SmtpMessage : IEmailMessage
    {
        public IList<String> ToAddresses { get; set; }
        public IList<String> BccAddresses { get; set; }
        public string MessageBody { get; set; }
        public string Subject { get; set; }
        public bool Send()
        {
            //Do the real work here
            return true;
        }
    }

    public class SmsMessage : IMessage
    {
        public IList<String> ToAddresses { get; set; }
        public string MessageBody { get; set; }
        public bool Send()
        {
            //Do the real work here
            return true;
        }
    }

With this design we have a nice crisp IMessage definition that will serve Client B very well in that the client does not depend on any methods it does not use.  We have created a “wider” interface Called IEmailMessage that inherits from IMessage for Team A to use so Team A can send Email or an SMS text message.

If we want to remove the requirement that a Client understand it needs to set the address list and message body before calling the send method we could also do this:

    public interface IMessage
    {
        bool Send(IList<String> toAddresses, string messageBody);
    }

    public interface IEmailMessage:IMessage
    {
        string Subject { get; set; }
        IList<String> BccAddresses { get; set; }
    }

    public class SmtpMessage : IEmailMessage
    {
        public IList<String> BccAddresses { get; set; }
        public string Subject { get; set; }
        public bool Send(IList<String> toAddresses, string messageBody)
        {
            //Do the real work here
            return true;
        }
    }

    public class SmsMessage : IMessage
    {
        public bool Send(IList<String> toAddresses, string messageBody)
        {
            //Do the real work here
            return true;
        }
    }

Think of an interface as reasons for a client to change.  The larger the interface, the more likely it is that you will need to change clients in the future.  An interesting  but positive side effect is that the smaller interface also “lowers the bar” for new servers that wish to implement the interface.  When the next great message medium comes along the new server only needs to implement a very small IMessage interface to work with clients already using other IMessage servers.  In summary, favor many client-specific interfaces over one larger general purpose interface.  Stay tuned in a future post we will refactor this code to better follow the Single Responsibility Principle.