PTOM: The Interface Segregation Principle

In following suite with the The Los Techies Pablo’s Topic of the Month – March: SOLID Principles, I chose to write a little about the The Interface Segregation Principle (ISP). As Chad pointed out with LSP, the ISP is also one of Robert ‘Uncle Bob’ Martin’s S.O.L.I.D design principles.

Basically ISP tells us that clients shouldn’t be forced to implement interfaces they don’t use. In other words, if you have an abstract class or an interface, then the implementers should not be forced to implement parts that they don’t care about.

I was having trouble thinking of a real world example for ISP but then was reminded about implementing a custom Membership Provider in ASP.NET 2.0. I had completely blocked that monstrosity out of my mind (for good reason).

The Membership Provider was a way to integrate with some of the ASP.NET’s built in management of users and its associated server controls. For me, it ended up being a lot more trouble than it was worth, but it turns out to be a good example of a fat interface. In order to implement your own Membership Provider you “simply” implement the abstract class MembershipProvider like so:

public class CustomMembershipProvider : MembershipProvider
{
    public override string ApplicationName
    {
        get
        {
            throw new Exception("The method or operation is not implemented.");
        }
        set
        {
            throw new Exception("The method or operation is not implemented.");
        }
    }

    public override bool ChangePassword(string username, string oldPassword, string newPassword)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool ChangePasswordQuestionAndAnswer(string username, string password, 
        string newPasswordQuestion, string newPasswordAnswer)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUser CreateUser(string username, string password, string email, 
        string passwordQuestion, string passwordAnswer, bool isApproved, object providerUserKey, 
        out MembershipCreateStatus status)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool DeleteUser(string username, bool deleteAllRelatedData)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool EnablePasswordReset
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override bool EnablePasswordRetrieval
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, 
        int pageSize, out int totalRecords)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, 
        int pageSize, out int totalRecords)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override int GetNumberOfUsersOnline()
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override string GetPassword(string username, string answer)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUser GetUser(string username, bool userIsOnline)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override string GetUserNameByEmail(string email)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override int MaxInvalidPasswordAttempts
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override int MinRequiredNonAlphanumericCharacters
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override int MinRequiredPasswordLength
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override int PasswordAttemptWindow
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override MembershipPasswordFormat PasswordFormat
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override string PasswordStrengthRegularExpression
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override bool RequiresQuestionAndAnswer
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override bool RequiresUniqueEmail
    {
        get { throw new Exception("The method or operation is not implemented."); }
    }

    public override string ResetPassword(string username, string answer)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool UnlockUser(string userName)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override void UpdateUser(MembershipUser user)
    {
        throw new Exception("The method or operation is not implemented.");
    }

    public override bool ValidateUser(string username, string password)
    {
        throw new Exception("The method or operation is not implemented.");
    }
}

Holy guacamole! That’s a lot of stuff. Sorry for the code puke there, but wanted you to feel a little pain as I did trying to actually implement this thing. Hopefully you didn’t get tired of scrolling through that and you’re still with me. ;)

It turns out that you don’t have to implement the parts you don’t need, but this clearly violates the Interface Segregation Principle. This interface is extremely fat and not cohesive. A better approach would have been to break it up into smaller interfaces that allow the implementers to only worry about the parts that they need. I’m not going to go into the details of splitting this up, but I think you get the idea.

Since I cannot think of another real world example, let’s look at a completely bogus example. Say you have the following code:

public abstract class Animal
{
    public abstract void Feed();
}

public class Dog : Animal
{
    public override void Feed()
    {
        // do something
    }
}

public class Rattlesnake : Animal
{
    public override void Feed()
    {
        // do something
    }
}

But then you realize that you have a need for some of the animals to be treated as pets and have them groomed. You may be tempted to do

public abstract class Animal
{
    public abstract void Feed();
    public abstract void Groom();
}

which would be fine for the Dog, but it may not be fine for the Rattlesnake (although I’m sure there is some freako out there that grooms their pet rattlesnake)

public class Rattlesnake : Animal
{
    public override void Feed()
    {
        // do something
    }

    public override void Groom()
    {
        // ignore - I'm not grooming a freaking rattlesnake
    }
}

Here we have violated the ISP by polluting our Animal interface. This requires us to implement a method that doesn’t make sense for the Rattlesnake object. A better choice would to implement an IPet interface which just Dog could implement and without affecting Rattlesnake. You might end up with something like this:

public interface IPet
{
    void Groom();
}

public abstract class Animal
{
    public abstract void Feed();
}

public class Dog : Animal, IPet
{
    public override void Feed()
    {
        // do something
    }

    public void Groom()
    {
        // do something
    }
}

public class Rattlesnake : Animal
{
    public override void Feed()
    {
        // do something
    }
}

I think the key is if you find yourself creating interfaces that don’t get fully implemented in its clients, then that’s a good sign that you’re violating the ISP. You can check out the link to this pdf for more complete information on the subject.

Technorati Tags: ,,

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Ray Houston

Ray is a software development leader and architect with 20 years hands-on experience. He enjoys finding elegant solutions to complex problems and delivering real value to customers. Ray is a speaker and contributor to community events.
This entry was posted in PTOM. Bookmark the permalink. Follow any comments here with the RSS feed for this post.

6 Responses to PTOM: The Interface Segregation Principle

  1. Jarod says:

    Nice! Another great contribution to the S.O.L.I.D posts you guys are doing.

    Though, somewhere down inside I really want to throw a RattleSnakeGroomException(“DOOOD!!!!! WTF are you doing grooming a snake?”); but I guess that kind of fun would violate ISP & LSP, so ill refrain.

  2. Colin Jack says:

    Although I’m a fan of Robert Martin I think I might have to use Yahoo Pipes to filter out all of these articles before my RSS gets swamped with them :)

    MembershipProvider is one of the WORST API I have ever seen and as you say ISP wise its a complete joke. It and the closely related API’s are and violate many of RM’s principles and in fact many of the framework design guidelines.

    I mean look at MembershipProvider.CreateUser (http://msdn2.microsoft.com/en-us/library/system.web.security.membershipprovider.createuser.aspx). First 5 arguments are strings, wonderful. Then a bool, an object and an output parameter that says whether it succeeded. Need I go on (I did go on in a MS Connect item for it which MS closed :https://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=159377)

    Small comment but I think when most people use the term implement in regards to an interface they think you mean the inheritor. You make it clear what you mean but I just thought it might be confusing.

  3. Ray Houston says:

    @Colin – ah, after re-reading I noticed I did overload the term “implement” quite a bit. Hopefully it won’t trip anybody up too much.

  4. Eric Swann says:

    I was messing around with the membership provider this week. I was writing a custom provider to wrap a Castle ActiveRecord implementation. It’s definitely a little over the top.

  5. Carl J says:

    I thought that the main point of the “I” in SOLID, was that all classes that implement an Interface, have to have the exact same methods and signatures, no more, and no less.

  6. Carl J says:

    Ignore that last comment. Its too early, and got slightly mixed up with the Liskov Substitution Principle. :)

Leave a Reply

Your email address will not be published. Required fields are marked *

*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>