Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C#

Interface Segregation Principle (ISP of SOLID in C#)- Walk through History

4.53/5 (24 votes)
12 May 2014CPOL3 min read 69.1K  
Interface Segregation Principle - Walk through History

Introduction

This tip explains what is Interface Segregation Principle and its uses. It is aimed at beginners and intermediate developers.

Interface Segregation Principle

The principle states that no client should be forced to depend on methods that it does not use (Wiki).

In other words, “What is the point in selling a horse saddle for one who does not own a horse?”

Disclaimer: The following discussion is inspired from Wikipedia. You will find the examples similar but elaborated for the sake of understanding.

History

Let's begin with a little bit of story.

Don't worry :) It's going to be interesting.

Let us say a company, Xerox had created a new all in one printer system. The printer system could do a wide variety of jobs like printing, scanning, stapling, faxing, etc.

Xerox hired developers to develop software for that printer machine. Soon the software was developed and printer machine was working great.

But when the time progressed, maintaining the software became difficult and further development was a nightmare. Why? Well, we will see that in a short span of time. J

What Did Xerox Do?

Xerox hired a consultant to look at what was the problem. Here comes our hero, Martin. We will see what the problem was, how Martin proposed a solution and the birth of ISP.

The problematic code snippet is as follows:

C#
// The code that violates ISP
interface IMachine
{
    public bool print(List<Item> item);
    public bool staple(List<Item> item);
    public bool fax(List<Item> item);
    public bool scan(List<Item> item);
    public bool photoCopy(List<Item> item);
}  
// Code implementing the IMachine interface. 

class Machine : IMachine
{
   public Machine()
   {
   }

   public bool print(List<Item> item)
   { 
      // Print the items.
      Console.WriteLine("All Items printed" + item.Count());
   }

   public bool staple(List<Item> item) 
   {
      // Staple the items.
      Console.WriteLine("Items stapled" + item.Count());
   }
 
   public bool fax(List<Item> item) 
   {
      // Fax the items.
      Console.WriteLine("All Items Faxed" + item.Count());
   }

   public bool scan(List<Item> item) 
   {
      // Scan the items.
      Console.WriteLine("All Items Scanned" + item.Count());
   }

   public bool photoCopy(List<Item> item) 
   {
      // Xerox the items.
      Console.WriteLine("All Items Photo copied" + item.Count()); 
   }
}  

Well, that is a straight forward code. But, wait!

Before scrolling down, try taking a minute to make a list of potential problems with the above code.

The Problems and the Pitfalls

Ok, here we go. J

Problems

1. Recompiling the entire code even when a minor change has to be made L

Even if you make a small modification, the entire code needs to be re-compiled wasting precious development time.

2. What happens when you supply this to a client who is interested only in printing job?

You are passing a DLL or a library which contains a bunch of unnecessary functions which are not all required to your client. He or she may get confused over these functions. Passing a printer function to a printer client makes sense. But, what good does it make passing a bunch of irrelevant functions?

3. Problem with “fat” Interfaces

Even though the Print functionality is the only needed function for the printer-client, he/she might end up implementing some bunch of unwanted functions. Why are you forcing your printer client to implement scanning functionality even when they don't want it?

So What Did Martin Do?

Martin suggested a solution that gave birth to Interface Segregation Principle.

Here comes that:

  1. Break down the fat interface to smaller and meaningful role interfaces.
  2. In the above example, let the IMachine interface contain all the broken down interfaces :)
  3. Inject the implementations of smaller interfaces to Machine class (Dependency Injection).
C#
// (The following interfaces are called as "Role Interfaces" as they serve their roles :))

interface IPrinter
{
    public bool Print(List<Item> Items);
}

class Printer : IPrinter
{
    public bool Print(List<Item> Items)
    {
       foreach(var item in Item)
          print(item);
    }

    //Other definitions...
}

interface IStaple
{
    public bool Staple(List<Item> Items);
}

class Staple : IStaple
{
    public bool Staple(List<Item> Items)
    {
       foreach(var item in Items) 
          Staple(item);
    }

    //Other definitions...
}

interface IFax
{    
    public bool Fax(List<Item> Items);
}

class Fax : IFax
{
    public bool Fax(List<Item> Items)
    {
        foreach(var item in Items) 
           Fax(item);
    }

    //Other definitions...
}

interface IScan
{
    public bool Scan(List<Item> Items);
}

class Scan : IScan
{
    public bool Scan(List<Item> Items)
    {
        foreach(var item in Items)
           Scan(item);
    }

    //Other definitions...
}

interface IPhotoCopy
{
    public bool PhotoCopy(List<Item> Items);
}

class PhotoCopy : IPhotoCopy
{
    public bool PhotoCopy(List<Item> Items)
    {
        foreach(var item in Items)
           PhotoCopy(item);
    }

    //Other definitions...
}

interface IMachine : IPrinter, IFax, IScan, IPhotoCopy,IStaple
{
    public bool print(List<Item> item);
    public bool staple(List<Item> item);
    public bool fax(List<Item> item);
    public bool scan(List<Item> item);
    public bool photoCopy(List<Item> item);
}

class Machine : IMachine
{
      private IPrinter printer {get;set;}
      private IFax fax {get;set;}
      private IScan scan  {get;set;}
      private IPhotoCopy photocopy {get;set;}
      private IStaple staple {get;set;}
  
      // Notice how the dependencies are injected through constructor (constructor dependency injection)
      public  Machine(IPrinter printer, IFax fax, IScan scan, IPhotoCopy photoCopy, IStaple staple)
      {
         this.Printer = printer;
        this.fax = fax;    
        this.scan = scan;
        this.photoCopy = photocopy;
        this.staple = staple;
          }

      public bool print(List<Item> item)
         { 
          // Print the items.
          Console.WriteLine("All Items printed" + item.Count());
      }

      public bool staple(List<Item> item) 
      {
          // Staple the items.
          Console.WriteLine("Items stapled" + item.Count());
      }
 
      public bool fax(List<Item> item) 
      {
          // Fax the items.
          Console.WriteLine("All Items Faxed" + item.Count());
      }

      public bool scan(List<Item> item) 
      {
          // Scan the items.
          Console.WriteLine("All Items Scanned" + item.Count());
      }

      public bool photoCopy(List<Item> item) 
      {
          // Xerox the items.
          Console.WriteLine("All Items Photo copied" + item.Count()); 
      }
}

What Happens Afterwards?

1. A printer only client comes. What to do?

It’s simple. Create an instance of Printer class and give to him/her.

C#
IPrinter printer = new Printer();

printer.print( //IDE shows that only the Print function is accessible.
// Clean, neat and separate.

Note that here we are not forcing him to use a library that has unwanted functionality (like scanning, faxing, etc.). Our client wants only the printer functionality and we give him what he asked for using our "IPrinter" role interface. Our client is now happy because he got what he wanted. :)

2. A client wanting all the functionalities arrives. What to do?

Easy enough.

C#
IPrinter printer = new Printer();     //Printer implements IPrinter
IScan scanner = new Scanner();
IFax fax = new Fax();
IPhotoCopy photocopy = new PhotoCopy();
IStaple staple = new Staple();

var allOneClient  = new Machine(printer, fax, scan, photoCopy, staple);

Feels good. Huh?

And with that, I end how Martin formulated ISP.

Final Words

The Wiki says:

“The interface-segregation principle (ISP) states that no client should be forced to depend on methods it does not use.”

  1. ISP splits interfaces which are very large into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them.
  2. Such shrunken interfaces are also called role interfaces.
  3. ISP is intended to keep a system decoupled and thus easier to refactor, change, and redeploy.

I hope the above example covers every point. Happy OODing :)

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)