Understanding the Interface Segregation Principle (ISP) with C#

In this article, we will explore the fourth principle in the SOLID design principles series: the Interface Segregation Principle (ISP). In case you missed the previous articles in this series, you can catch up here.

The Interface Segregation Principle encourages developers to create specific and focused interfaces rather than large, bloated ones. By doing so, we ensure that classes using these interfaces are only exposed to the methods they actually need. This principle helps to avoid the "fat interface" problem and keeps our codebase clean, modular, and easy to maintain.

Interface Segregation Principle

The Interface Segregation Principle states that.

"Clients should not be forced to depend on interfaces they do not use."

In simpler terms, instead of having one large interface that serves multiple purposes, we should break it down into smaller, more specific interfaces. This ensures that implementing classes are only concerned with the methods that are relevant to them.

Example

Let’s look at a real-world example to understand ISP in the context of a Library Management System.

In a library, different items like books, magazines, DVDs, and newspapers can be lent out to members. However, not all items have the same lending rules. For instance, while books and DVDs can be lent out for several days, newspapers may not be available for lending at all.

To model this, let's start with a generic interface that might initially seem reasonable.

public interface ILibraryItem
{
    void CheckOut();
    void ReturnItem();
    void Reserve();
    void Renew();
}

This interface is an example of a "fat interface" as it is broad and assumes that every item in the library can be checked out, reserved, and renewed. However, not all items share these characteristics. For example, newspapers can't be reserved or renewed, and some reference books might only be used within the library.

The Problem with a Fat Interface

If we force all items to implement the ILibraryItem interface, we introduce unnecessary complexity and potential errors, for instance.

  • A Newspaper class might have to implement methods like Reserve() and Renew() even though these operations are not applicable to newspapers.
  • The DVD class might have specific rules for renewing that differ from those of a Book.

This violates the ISP because we're forcing classes to depend on methods they don't need.

Applying the Interface Segregation Principle

To adhere to the ISP, we should break down the ILibraryItem interface into more specific interfaces that align with the behavior of different library items.

Here’s how we can do that.

public interface ILendable
{
    void CheckOut();
    void ReturnItem();
}
public interface IReservable
{
    void Reserve();
}
public interface IRenewable
{
    void Renew();
}

We created separate interfaces ILendable, IReservable, and IRenewable, which focus on specific behaviors. Now, we can implement these interfaces in our library item classes as needed.

public class Book : ILendable, IReservable, IRenewable
{
    public void CheckOut()
    {
        // Logic to check out the book
    }
    public void ReturnItem()
    {
        // Logic to return the book
    }
    public void Reserve()
    {
        // Logic to reserve the book
    }
    public void Renew()
    {
        // Logic to renew the book
    }
}
public class DVD : ILendable, IRenewable
{
    public void CheckOut()
    {
        // Logic to check out the DVD
    }
    public void ReturnItem()
    {
        // Logic to return the DVD
    }
    public void Renew()
    {
        // Logic to renew the DVD
    }
}
public class Newspaper : ILendable
{
    public void CheckOut()
    {
        // Logic to check out the newspaper
    }
    public void ReturnItem()
    {
        // Logic to return the newspaper
    }
}

In this refactored design.

  • The Book class implements all three interfaces (ILendable, IReservable, and IRenewable) because books can be lent, reserved, and renewed.
  • The DVD class implements ILendable and IRenewable because DVDs can be lent and renewed but not reserved.
  • The Newspaper class only implements ILendable because newspapers can be checked out but not reserved or renewed.

By separating concerns into distinct interfaces, we've made our code more flexible and easier to maintain. For example, if we later decide to change the way renewal operation is done for DVDs, we can modify the IRenewable interface without affecting any other parts of the system. Similarly, if we want to add reservations for DVDs, we can simply implement the IReservable interface in the DVD class without having to worry about breaking anything else.

Summary

Following the Interface Segregation Principle helps ensure that your code remains loosely coupled and easy to modify. By creating small, focused interfaces tailored to specific client needs, you can reduce dependencies and increase re-usability throughout your application.


Similar Articles