Understanding the Interface Segregation Principle (ISP) with C#
In this blog post, we will explore the fourth principle in the SOLID design principles series: the Interface Segregation Principle (ISP). In case you missed the previous posts 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 likeReserve()
andRenew()
even though these operations are not applicable for newspapers.The
DVD
class might have specific rules for renewing that differ from those of aBook
.
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 focuses 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
, andIRenewable
) because books can be lent, reserved, and renewed.The
DVD
class implementsILendable
andIRenewable
because DVDs can be lent and renewed but not reserved.The
Newspaper
class only implementsILendable
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 renew 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
In conclusion, 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.
Subscribe to my newsletter
Read articles from Geo J Thachankary directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by