Search code examples
c#oopinterfacesolid-principles

Implementing ISP design pattern in C#


I am trying to use Robert C. Martin principle of ISP.

From Wikipedia,

The ISP was first used and formulated by Robert C. Martin while consulting for Xerox. Xerox had created a new printer system that could perform a variety of tasks such as stapling and faxing. The software for this system was created from the ground up. As the software grew, making modification became more and more difficult so that even the smallest change would take a redeployment cycle of an hour, which made development nearly impossible.

The design problem was that a single Job class was used by almost all of the tasks. Whenever a print job or a stapling job needed to be performed, a call was made to the Job class. This resulted in a 'fat' class with multitudes of methods specific to a variety of different clients. Because of this design, a staple job would know about all the methods of the print job, even though there was no use for them.

The solution suggested by Martin utilized what is called the Interface Segregation Principle today. Applied to the Xerox software, an interface layer between the Job class and its clients was added using the Dependency Inversion Principle. Instead of having one large Job class, a Staple Job interface or a Print Job interface was created that would be used by the Staple or Print classes, respectively, calling methods of the Job class. Therefore, one interface was created for each job type, which were all implemented by the Job class.

What I am trying to understand is how the system functioned and what Martin proposed to change it.

interface IJob
{
    bool DoPrintJob();
    bool DoStaplingJob();
    bool DoJob1();
    bool DoJob2();
    bool DoJob3();
}

class Job : IJob
{
  // implement all IJob methods here.
}

var printClient = new Job(); // a class implemeting IJob
printClient.DoPrintJob();  // but `printClient` also knows about DoStaplingJob(), DoJob1(), DoJob2(), DoJob3() also.

I could try up to this point and got stuck up here

an interface layer between the Job class and its clients was added using the Dependency Inversion Principle - Wikipedia lines - (Interface layer ?)

1Instead of having one large Job class, a Staple Job interface or a Print Job interface was created that would be used by the Staple or Print classes, respectively, calling methods of the Job class - ( then calling methods of the Job class - ok, create separate interface and then why to call the methods of job class ?)

What Martin did next? (Some corrected code skeletons would help me understand this).

Based on the answers, I was able to proceed as below. Thanks Sergey and Christos.

interface IPrintJob
{
  bool DoPrintJob();
}

interface IStapleJob
{
  bool DoStapleJob();
}

interface IJob : IPrintJob, IStapleJob
{
  bool DoPrintJob();
  bool DoStaplingJob();
}

var printClient = new PrintJob(); //PrintJob implements the IPrintJob interface 
var stapleClient = new StableJob(); // StapleJob implements the IStapleJob interface

Ok Great. What does the IJob interface do, Why is it used ? It can be removed right?


Solution

  • ISP is not a design pattern - its a design principle. And it helps to avoid implementing interfaces which are not required by clients. E.g. in your case you have client which needs only printing. But you have IJob interface with bunch of methods which don't needed by this client. Why would I implement DoStaplingJob, DoJob1, DoJob2 and DoJob3 if I want only printing? So, solution is creating small interface which satisfies my need:

    public interface IPrintingJob
    {
       bool DoPrintJob();
    }
    

    Original interface will look like:

    public interface IJob : IPrintingJob
    {
        bool DoStaplingJob();
        bool DoJob1();
        bool DoJob2();
        bool DoJob3();
    }
    

    Now all clients which want only printing, will implement IPrintginJob interface, without being bothered with other members of IJob interface. You can continue spliting IJob interface to smaller interfaces, if you will have clients which don't need whole functionality of IJob interface.

    UPDATE: From client point of view. Depending on big interface is not very convenient. E.g. you have client which wants only printing. You can depend on IJob interface and pass Job class instance to this client:

    public void Foo(IJob job)
    {    
        job. // intellisense will show confusing bunch of members you don't need here
    }
    

    With many small interfaces, you can depend only on IPrintingJob interface, and still pass big Job class as implementation of this interface:

    public  void Foo(IPrintingJob printingJob)
    {
        printingJob. // intellisense will show single member. easy and handy
    }
    

    Another benefit is easy refactoring. Later you can extract printing functionality from Job class to other small class like PrintingJob. And you will be able to pass its instance to clients which need only printing.