Interface Segregation Principle in C#

Interface Segregation Principle in C# with Examples

In this article, I am going to discuss the Interface Segregation Principle in C# with Examples. Please read our previous article before proceeding to this article, where we discussed the Liskov Substitution Principle in C# with Examples. The letter I in the SOLID Design Principle stands for Interface Segregation Principle, also known as ISP. As part of this article, we are going to discuss the following pointers in detail.

  1. What is Interface Segregation Principle in C#?
  2. Example without using the Interface Segregation Principle in C#.
  3. Example using the Interface Segregation Principle in C#.
  4. How to Use Interface Segregation Principle in C#?
What is the Interface Segregation Principle in C#?

The Interface Segregation Principle states Clients should not be forced to implement any methods they don’t use. Rather than one fat interface, numerous little interfaces are preferred based on groups of methods, with each interface serving one submodule.

Let us break down the above definition into two parts.

  1. First, no class should be forced to implement any method(s) of an interface they don’t use.
  2. Secondly, instead of creating large or, you can say, fat interfaces, create multiple smaller interfaces with the aim that the clients should only think about the methods that are of interest to them.

As per the Single Responsibility Principle of SOLID, interfaces should also have a single responsibility, like classes. That means we shouldn’t force any class to implement any method(s) that they don’t require.

Example to Understand Interface Segregation Principle in C#.

Let us understand Interface Segregation Principle in C# with an example. Please have a look at the following diagram. Here, you can see we have one interface and two classes implementing that Interface.

Example to Understand Interface Segregation Principle in C#

As you can see in the above diagram, we have an interface, i.e., IPrinterTasks declared with four methods. Now if any class wants to implement this interface, then that class should and must have to provide the implementation to all four methods of the IPrinterTasks interface. As you can see in the above diagram, we have two classes HPLaserJetPrinter and LiquidInkjetPrinter, who want the printer service.

But the requirement is the HPLaserJetPrinter wants all the services provided by the IPrinterTasks while the LiquidInkjetPrinter wants only the Print and Scan service of the printer. As we have declared all the methods within the IPrinterTasks interface, then it is mandatory for the LiquidInkjetPrinter class to provide implementation to Scan and Print methods along with the Fax and PrintDulex method, which are not required by the class. This violates the Interface Segregation Principle in C#, forcing the class to provide the implementation they don’t require.

Example Without using the Interface Segregation Principle in C#:

Let us first see the example without following the Interface Segregation Principle, and then we will see the problem. and finally, we will rewrite the same example by following Interface Segregation Principle using C# Language.

IPrinterTasks.cs

Create a class file with the name IPrinterTasks.cs and then copy and paste the following code into it. Here, IPrinterTasks is an interface and contains the declaration of four methods. By default, interface methods are public and abstract, so the child class of this interface needs to provide the implementation of all these methods.

namespace SOLID_PRINCIPLES.ISP
{
    public interface IPrinterTasks
    {
        void Print(string PrintContent);
        void Scan(string ScanContent);
        void Fax(string FaxContent);
        void PrintDuplex(string PrintDuplexContent);
    }
}
HPLaserJetPrinter.cs

Next, create a class file with the name HPLaserJetPrinter.cs and then copy and paste the following code into it. Here, the HPLaserJetPrinter implements the IPrinterTasks interface and provides implementations for all four methods. This class requires all printer services, so there is no issue in providing implementations for all interface methods.

using System;
namespace SOLID_PRINCIPLES.ISP
{
    public class HPLaserJetPrinter : IPrinterTasks
    {
        public void Print(string PrintContent)
        {
            Console.WriteLine("Print Done");
        }

        public void Scan(string ScanContent)
        {
            Console.WriteLine("Scan content");
        }

        public void Fax(string FaxContent)
        {
            Console.WriteLine("Fax content");
        }

        public void PrintDuplex(string PrintDuplexContent)
        {
            Console.WriteLine("Print Duplex content");
        }
    }
}
LiquidInkjetPrinter.cs

Next, create a class file with the name LiquidInkjetPrinter.cs and then copy and paste the following code into it. Here, the LiquidInkjetPrinter implements the IPrinterTasks interface and provides implementations for all four methods. But, this class only requires the Print and Scan services. This class does not require Fax and PrintDuplex services, but, still, it is implementing these two methods. This violates the Interface Segregation Principle as we are forcing the class to implement two methods that they don’t require.

using System;
namespace SOLID_PRINCIPLES.ISP
{
    class LiquidInkjetPrinter : IPrinterTasks
    {
        public void Print(string PrintContent)
        {
            Console.WriteLine("Print Done");
        }

        public void Scan(string ScanContent)
        {
            Console.WriteLine("Scan content");
        }

        public void Fax(string FaxContent)
        {
            throw new NotImplementedException();
        }

        public void PrintDuplex(string PrintDuplexContent)
        {
            throw new NotImplementedException();
        }
    }
}
Why are we Facing this problem?

We are facing the above problem because we are declaring all the methods in a single class. And as per Inheritance Rules, the child class will be responsible for providing implementations for all interface methods. Because of this, LiquidInkjetPrinter class provides implementation to all IPrinterTasks Interface methods. We can overcome this problem by following the Interface Segregation Principle. Let us proceed and try to understand how we can rewrite the same example by following Interface Segregation Principle using C# Language.

Example using Interface Segregation Principle in C#:

Please have a look at the following image. As you can see in the image below, we have split that big interface into three small interfaces. Each interface now has some specific purpose. 

Example using Interface Segregation Principle in C#

Now if any class wants all the printer services, then that class needs to implement all three interfaces. In our example, HPLaserJetPrinter wants all the printer services. So, the HPLaserJetPrinter class needs to implement all three interfaces and provide implementations of all interface methods, as shown in the below image.

Example using Interface Segregation Principle in C#

Now, if any class wants the Scan and Print services, then that class needs to implement only the IPrinterTasks interfaces. In our example, LiquidInkjetPrinter wants only the Scan and Print services. So, the LiquidInkjetPrinter class needs to implement only the IPrinterTasks interfaces and provide implementations for Print and Scan methods, as shown in the image below.

What is Interface Segregation Principle in C#?

The Complete Example Code is Given Below:

First, modify the IPrinterTasks.s class file as follows. Here, you can see we are splitting the big interface into three interfaces. Now, Each interface now has some specific purpose. And based on the requirement, the child class will implement 1, 2, or all the interfaces.

namespace SOLID_PRINCIPLES.ISP
{
    public interface IPrinterTasks
    {
        void Print(string PrintContent);
        void Scan(string ScanContent);
    }
    interface IFaxTasks
    {
        void Fax(string content);
    }
    interface IPrintDuplexTasks
    {
        void PrintDuplex(string content);
    }
}

Next, modify the HPLaserJetPrinter.cs class file as follows. The HPLaserJetPrinter printer class requires all the printer services, hence implementing all three interfaces and providing implementation to all four methods.

using System;
namespace SOLID_PRINCIPLES.ISP
{
    public class HPLaserJetPrinter : IPrinterTasks, IFaxTasks, IPrintDuplexTasks
    {
        public void Print(string PrintContent)
        {
            Console.WriteLine("Print Done");
        }
        public void Scan(string ScanContent)
        {
            Console.WriteLine("Scan content");
        }
        public void Fax(string FaxContent)
        {
            Console.WriteLine("Fax content");
        }
        public void PrintDuplex(string PrintDuplexContent)
        {
            Console.WriteLine("Print Duplex content");
        }
    }
}

Next, modify the LiquidInkjetPrinter.cs class file as follows. The LiquidInkjetPrinter printer class requires only the Print and Scan Printer services and hence implements only the IPrinterTasks interface and provides implementation to Print and Scan methods.

using System;
namespace SOLID_PRINCIPLES.ISP
{
    class LiquidInkjetPrinter : IPrinterTasks
    {
        public void Print(string PrintContent)
        {
            Console.WriteLine("Print Done");
        }
        public void Scan(string ScanContent)
        {
            Console.WriteLine("Scan content");
        }
    }
}

Now, you can see the LiquidInkjetPrinter class is not providing implementation to the Fax and PrintDuplex method as these services are not required by the LiquidInkjetPrinter class. That means now our application design follows the Interface Segregation Principle. Now, you can test the functionality of the two classes by modifying the Program class code as follows.

using System;
namespace SOLID_PRINCIPLES.ISP
{
    public class Program
    {
        static void Main(string[] args)
        {
            //Using HPLaserJetPrinter we can access all Printer Services
            HPLaserJetPrinter hPLaserJetPrinter = new HPLaserJetPrinter();
            hPLaserJetPrinter.Print("Printing");
            hPLaserJetPrinter.Scan("Scanning");
            hPLaserJetPrinter.Fax("Faxing");
            hPLaserJetPrinter.PrintDuplex("PrintDuplex");

            //Using LiquidInkjetPrinter we can only Access Print and Scan Printer Services
            LiquidInkjetPrinter liquidInkjetPrinter = new LiquidInkjetPrinter();
            liquidInkjetPrinter.Print("Printing");
            liquidInkjetPrinter.Scan("Scanning");

            Console.ReadKey();
        }
    }
}

When you run the above code, you will get the following output.

Complete Example Code

Note: A solution to having a better code always comes down to a word small: small method, small class, small interface. So, if you split your code into smaller chunks, it will be easier to change and maintain it.

How to Use Interface Segregation Principle in C#?

The Interface Segregation Principle (ISP) states that a class should not be forced to implement interfaces it does not use. In other words, interfaces should be designed to be specific to the needs of the implementing classes. This principle aims to prevent unnecessary coupling between classes and interfaces, leading to more maintainable and cohesive code. Here’s how to use the Interface Segregation Principle effectively in C#:

  • Identify Client Needs: Determine the specific behaviors or methods that each client (class) requires from an interface. Avoid creating large, monolithic interfaces that cater to multiple clients.
  • Design Fine-Grained Interfaces: Create smaller, more focused interfaces that contain only the methods relevant to a particular client’s needs. This prevents classes from implementing methods they don’t use.
  • Avoid Fat Interfaces: Refrain from designing interfaces with a multitude of methods that serve different purposes. If an interface becomes too large, consider breaking it down into smaller, cohesive interfaces.
  • Create Specific Interfaces: Design interfaces tailored to the implementing classes’ specific context or domain. This ensures that classes are only required to implement methods relevant to their responsibilities.
  • Prefer Composition: Instead of creating a single interface that covers multiple aspects of a class’s behavior, consider using composition to assemble smaller interfaces and components.
  • Extract Common Behaviors: If multiple classes share a common set of methods, extract those methods into a separate interface. This prevents code duplication and promotes reuse.
  • Refactor Existing Interfaces: If you have existing interfaces that violate the ISP, refactor them to create smaller, more cohesive interfaces that align with the needs of the implementing classes.
  • Use Default Implementations: In C# 8 and later, you can use default implementations in interfaces to provide a basic implementation for methods. This allows classes to override methods as needed selectively.
  • Apply Dependency Injection: Use dependency injection to provide implementations for the specific interfaces that a class needs. This allows for loose coupling and easy substitution of implementations.
  • Test for ISP Compliance: Test whether a class adheres to the ISP by examining its dependencies on interfaces. If a class doesn’t use all methods of an interface, it might be a sign of ISP violation.

Here’s a simple example demonstrating the use of the Interface Segregation Principle in C#. In the below example, the Worker class implements the IWorker interface, and the SuperWorker class implements both IWorker and IEater interfaces. The Manager class depends only on the IWorker interface, following the Interface Segregation Principle by not forcing classes to implement interfaces they don’t need.

namespace SOLID_PRINCIPLES.ISP
{
    public interface IWorker
    {
        void Work();
    }

    public interface IEater
    {
        void Eat();
    }

    public class Worker : IWorker
    {
        public void Work()
        {
            // Perform work-related tasks
        }
    }

    public class SuperWorker : IWorker, IEater
    {
        public void Work()
        {
            // Perform complex work tasks
        }

        public void Eat()
        {
            // Eat during breaks
        }
    }

    public class Manager
    {
        private IWorker _worker;

        public Manager(IWorker worker)
        {
            _worker = worker;
        }

        public void Manage()
        {
            _worker.Work();
        }
    }
}

By following these guidelines, you can create more focused and cohesive interfaces that improve the maintainability and flexibility of your codebase in accordance with the Interface Segregation Principle.

Advantages and Disadvantages of Interface Segregation Principle in C#

The Interface Segregation Principle (ISP) is one of the SOLID principles of object-oriented design, focusing on designing small, cohesive interfaces that cater to the specific needs of implementing classes. Let’s explore the advantages and disadvantages of following the Interface Segregation Principle in C#:

Advantages:
  1. Reduced Coupling: Interfaces designed with the ISP in mind are more focused and contain only the methods relevant to specific classes. This leads to lower coupling between classes and interfaces, promoting better separation of concerns.
  2. Improved Maintainability: Small, specialized interfaces are easier to understand, implement, and maintain. Changes to one interface are less likely to impact unrelated classes.
  3. Flexibility in Implementations: Implementing classes have the freedom to choose which specific interfaces to implement based on their responsibilities. This allows for more flexible and modular code.
  4. Enhanced Reusability: Interfaces that align closely with the requirements of implementing classes lead to better code reuse. Implementing classes can easily fit into different contexts by selecting relevant interfaces.
  5. Avoiding Fat Interfaces: Following the ISP prevents the creation of “fat” interfaces with a large number of methods that not all classes need. This keeps interfaces clean and focused.
  6. Clearer Intent: Specific interfaces convey the intent and responsibilities of implementing classes more clearly. This makes the codebase more self-documenting and easier to understand.
Disadvantages:
  1. Interface Proliferation: Adhering to the ISP might lead to numerous small interfaces, making the codebase more complex to navigate and understand.
  2. Design Effort: Designing and maintaining a higher number of smaller interfaces might require additional effort compared to creating larger, more general interfaces.
  3. Potential for Overhead: In some cases, the overhead of implementing multiple small interfaces might be higher than implementing a single larger interface, especially if implementations share common methods.
  4. Compatibility Issues: If interfaces are broken down too much, providing default implementations for common methods might become difficult. This could lead to issues with backward compatibility.
  5. Designing Contracts: Creating precise and effective interfaces requires careful consideration of the needs of implementing classes, which might involve more upfront design effort.
  6. Balancing Abstraction: Striking the right balance between fine-grained interfaces and generalization can be challenging, as overly specific interfaces might not cater to all potential use cases.

Adhering to the Interface Segregation Principle offers several significant benefits, such as reduced coupling, improved maintainability, and enhanced flexibility. However, there’s a trade-off between interface granularity and design complexity. Balancing these factors requires thoughtful design decisions to ensure the principle’s advantages outweigh its potential disadvantages in your specific application context.

In the next article, I am going to discuss the Dependency Inversion Principle in C# with Examples. I explain the Interface Segregation Principle in C# with Examples in this article. I hope you enjoy this Interface Segregation Principle in C# with Examples article.

8 thoughts on “Interface Segregation Principle in C#”

Leave a Reply

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