Interface Segregation Principle

Interface Segregation Principle

Definition

The client shouldn't be forced to depend on methods that it doesn't use. Or it can be stated more positively: clients should depend only on the smallest set of interface features: the fewest methods and attributes.

The key to identifying when to apply ISP is to look into the method and define the collaborators. Each collaborator should have the narrowest interface possible to the target object. let's have an example.

Example

Let's imagine that we want to design an e-commerce app and we have a class called Item, with some attributes:

class Item {
int Id {get; set;}
string Name {get; set;}
double Price {get; set;}
}

And also we have some subclasses inherited from Item class to represent the different types of items that we might have in our application:

class FoodItem : Item {}
class ClothItem : Item {}
class ElctronicItem : Item {}

We notice that we have common behaviors that we want to share among those classes so we can create an interface to hold those behaviors.

interface IItemActions
{
public Retire()
public AddToCart()
public Display()
public Save()
}

Then while designing other parts of the application we thought hey, in this interface we have some useful behaviors we want to share with other classes.

For example, we want the Order class to have Display() and Save() methods but we don't need the other two behaviors.

ISP suggests that we need to segregate this interface so that the order class avoids implementing methods that it doesn't use.

Additional Example: Issue Tracking

The below example is taken from the book (Designing Evolvable Web APIs with ASP.NET) we will try to demonstrate the use of ISP.

Separating Representation

public class Issue
{
 public string Id { get; set; }
 public string Title { get; set; }
 public string Description { get; set; }
 public IssueStatus Status { get; set; }
}
public interface IIssueStore
{
 Task<IEnumerable<Issue>> FindAsync();
 Task<Issue> FindAsync(string issueId);
 Task<IEnumerable<Issue>> FindAsyncQuery(string searchText);
 Task UpdateAsync(Issue issue);
 Task DeleteAsync(string issueId);
 Task CreateAsync(Issue issue);
}

The Issue class is a data model and contains data that is persisted for an issue in the store. We are using IIssueStore interface for the persistence representation. The crud operations are separated from the Issue methods.

Using Factory

public class IssueState
{
public IssuesState()
 {
 Links = new List<Link>();
 }
 public string Id { get; set; }
 public string Title { get; set; }
 public string Description { get; set; }
 public IssueStatus Status { get; set; }
 public IList<Link> Links { get; private set; }
}

public class IssueStateFactory : IStateFactory<Issue, IssueState> // <1>
{
 private readonly IssueLinkFactory _links;
 public IssueStateFactory(IssueLinkFactory links){}
 public IssueState Create(Issue issue){}
}

Here we are using the factory to construct the class to be used in the application. The interface for constructing the object IStateFactory is separate from the class. That means we are separating the code to construct the object from the methods of the object's internal behaviors related to the application.

The writer added "Notice the IssueState class has the same members as the Issue class with the addition of a collection of links. You might wonder why the IssueState class doesn’t inherit from Issue. The answer is to have a better separation of concerns. If IssueState inherits from Issue, then it is tightly coupled, meaning any changes to Issue will affect it. Evolvability is one of the qualities we want for the system; having good separation contributes to this, as parts can be modified independently of one another."

References

Book: Designing Evolvable Web APIs with ASP.NET: Harnessing the Power of the Web

Course: Learning S.O.L.I.D. Programming Principles by Steven Lott