Вопрос

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?

Это было полезно?

Решение

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.

Другие советы

At first the ISP prinicple states, as we read on Wikipedia:

no client should be forced to depend on methods it does not use

That being said, we should only declare interfaces that will be consisted of members, that will be used all from the types that will implement them. So intead of having one big interface consisting of 10 methods signatures, which will will not be implemented by all the the types that implement the interface is a bad practice. Hence you have to split this big interface to smaller one and each type that wants to implement a specific behaviour should implement the corresponding interface. That makes things more clear and more modular.

A code exammple it could make the things more clear.

Let we have the following interface:

public interface BigInterface
{
    void MethodA();
    void MethodB();
    void MethodC();
    void MethodD();
    void MethodE();
}

and let we have two classes:

public class classA : BigInterface
{

}

and

public class classB : BigInterface
{

}

Now both classes should provide an implementation of the five methods of the BigInterface, despite the fact the a subset of the methods makes sense for classA and another subset makes sense for classB. That's certainly a really bad practice, if you have implemented something like this.

In order to be more concrete, let's say that both classes have to implement methodA and methodB and only classA have to implement methodC and only classB have to implement methodD and methodE. Then according to the ISP principle you could organize you code to the following one:

public interface ICommomInterface
{
    void MethodA();
    void MethodB();
}

public interface ISpecificInterface1
{
    void MethodC();
}

public interface ISpecificInterface2
{
    void MethodD();
    void MethodE();
}

and the classes declaration's should be:

public class classA: ICommonInterface, ISpecificInterface1
{

}

public class classB: ICommonInterface, ISpecificInterface2
{

}

Note: As correctly, Sergey posted before me, ISP is a principle and it is not a design pattern.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top