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."); } }<span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> ChangePassword(<span style="color: #0000ff">string</span> username, <span style="color: #0000ff">string</span> oldPassword, <span style="color: #0000ff">string</span> newPassword) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> ChangePasswordQuestionAndAnswer(<span style="color: #0000ff">string</span> username, <span style="color: #0000ff">string</span> password, <span style="color: #0000ff">string</span> newPasswordQuestion, <span style="color: #0000ff">string</span> newPasswordAnswer) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> MembershipUser CreateUser(<span style="color: #0000ff">string</span> username, <span style="color: #0000ff">string</span> password, <span style="color: #0000ff">string</span> email, <span style="color: #0000ff">string</span> passwordQuestion, <span style="color: #0000ff">string</span> passwordAnswer, <span style="color: #0000ff">bool</span> isApproved, <span style="color: #0000ff">object</span> providerUserKey, <span style="color: #0000ff">out</span> MembershipCreateStatus status) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> DeleteUser(<span style="color: #0000ff">string</span> username, <span style="color: #0000ff">bool</span> deleteAllRelatedData) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> EnablePasswordReset { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> EnablePasswordRetrieval { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> MembershipUserCollection FindUsersByEmail(<span style="color: #0000ff">string</span> emailToMatch, <span style="color: #0000ff">int</span> pageIndex, <span style="color: #0000ff">int</span> pageSize, <span style="color: #0000ff">out</span> <span style="color: #0000ff">int</span> totalRecords) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> MembershipUserCollection FindUsersByName(<span style="color: #0000ff">string</span> usernameToMatch, <span style="color: #0000ff">int</span> pageIndex, <span style="color: #0000ff">int</span> pageSize, <span style="color: #0000ff">out</span> <span style="color: #0000ff">int</span> totalRecords) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> MembershipUserCollection GetAllUsers(<span style="color: #0000ff">int</span> pageIndex, <span style="color: #0000ff">int</span> pageSize, <span style="color: #0000ff">out</span> <span style="color: #0000ff">int</span> totalRecords) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">int</span> GetNumberOfUsersOnline() { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">string</span> GetPassword(<span style="color: #0000ff">string</span> username, <span style="color: #0000ff">string</span> answer) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> MembershipUser GetUser(<span style="color: #0000ff">string</span> username, <span style="color: #0000ff">bool</span> userIsOnline) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> MembershipUser GetUser(<span style="color: #0000ff">object</span> providerUserKey, <span style="color: #0000ff">bool</span> userIsOnline) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">string</span> GetUserNameByEmail(<span style="color: #0000ff">string</span> email) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">int</span> MaxInvalidPasswordAttempts { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">int</span> MinRequiredNonAlphanumericCharacters { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">int</span> MinRequiredPasswordLength { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">int</span> PasswordAttemptWindow { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> MembershipPasswordFormat PasswordFormat { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">string</span> PasswordStrengthRegularExpression { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> RequiresQuestionAndAnswer { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> RequiresUniqueEmail { get { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">string</span> ResetPassword(<span style="color: #0000ff">string</span> username, <span style="color: #0000ff">string</span> answer) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> UnlockUser(<span style="color: #0000ff">string</span> userName) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">void</span> UpdateUser(MembershipUser user) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); } <span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">bool</span> ValidateUser(<span style="color: #0000ff">string</span> username, <span style="color: #0000ff">string</span> password) { <span style="color: #0000ff">throw</span> <span style="color: #0000ff">new</span> Exception(<span style="color: #006080">"The method or operation is not implemented."</span>); }}
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 }<span style="color: #0000ff">public</span> <span style="color: #0000ff">override</span> <span style="color: #0000ff">void</span> Groom() { <span style="color: #008000">// ignore - I'm not grooming a freaking rattlesnake</span> }}
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 }
<span style="color: #0000ff">public</span> <span style="color: #0000ff">void</span> Groom() { <span style="color: #008000">// do something</span> }}
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.
