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:
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);
}
class Machine : IMachine
{
public Machine()
{
}
public bool print(List<Item> item)
{
Console.WriteLine("All Items printed" + item.Count());
}
public bool staple(List<Item> item)
{
Console.WriteLine("Items stapled" + item.Count());
}
public bool fax(List<Item> item)
{
Console.WriteLine("All Items Faxed" + item.Count());
}
public bool scan(List<Item> item)
{
Console.WriteLine("All Items Scanned" + item.Count());
}
public bool photoCopy(List<Item> item)
{
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:
- Break down the fat interface to smaller and meaningful role interfaces.
- In the above example, let the
IMachine
interface contain all the broken down interfaces :) - Inject the implementations of smaller interfaces to Machine class (Dependency Injection).
interface IPrinter
{
public bool Print(List<Item> Items);
}
class Printer : IPrinter
{
public bool Print(List<Item> Items)
{
foreach(var item in Item)
print(item);
}
}
interface IStaple
{
public bool Staple(List<Item> Items);
}
class Staple : IStaple
{
public bool Staple(List<Item> Items)
{
foreach(var item in Items)
Staple(item);
}
}
interface IFax
{
public bool Fax(List<Item> Items);
}
class Fax : IFax
{
public bool Fax(List<Item> Items)
{
foreach(var item in Items)
Fax(item);
}
}
interface IScan
{
public bool Scan(List<Item> Items);
}
class Scan : IScan
{
public bool Scan(List<Item> Items)
{
foreach(var item in Items)
Scan(item);
}
}
interface IPhotoCopy
{
public bool PhotoCopy(List<Item> Items);
}
class PhotoCopy : IPhotoCopy
{
public bool PhotoCopy(List<Item> Items)
{
foreach(var item in Items)
PhotoCopy(item);
}
}
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;}
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)
{
Console.WriteLine("All Items printed" + item.Count());
}
public bool staple(List<Item> item)
{
Console.WriteLine("Items stapled" + item.Count());
}
public bool fax(List<Item> item)
{
Console.WriteLine("All Items Faxed" + item.Count());
}
public bool scan(List<Item> item)
{
Console.WriteLine("All Items Scanned" + item.Count());
}
public bool photoCopy(List<Item> item)
{
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.
IPrinter printer = new Printer();
printer.print(
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.
IPrinter printer = new Printer();
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.”
- 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.
- Such shrunken interfaces are also called role interfaces.
- 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 :)