The Interface Segregation Principle

Introduction

 In this article we will examine yet another structural principle: the Interface Segregation
Principle (ISP). This principle deals with the disadvantages of “fat” interfaces.
Classes that have “fat” interfaces are classes whose interfaces are not cohesive. In other words, the interfaces of the class can be broken up into groups of member functions. Each
group serves a different set of clients. Thus some clients use one group of member functions,
and other clients use the other groups.
The ISP acknowledges that there are objects that require non-cohesive interfaces;
however it suggests that clients should not know about them as a single class. Instead, clients
should know about abstract base classes that have cohesive interfaces. Some languages
refer to these abstract base classes as “interfaces”, “protocols” or “signatures”.
In this article we will discuss the disadvantages of “fat” or “polluted” interfacse. We
will show how these interfaces get created, and how to design classes which hide them.
Finally we will present a case study in will employ the ISP to correct it.
  

Interface Pollution 

Consider a security system. In this system there are Door objects that can be locked and
unlocked, and which know whether they are open or closed. (See Listing 1).

Listing 1

Security Door
class Door
{
public:
virtual void Lock() = 0;
virtual void Unlock() = 0;
virtual bool IsDoorOpen() = 0;
};
This class is abstract so that clients can use objects that conform to the Door interface,
without having to depend upon particular implementations of Door.
Now consider that one such implementation. TimedDoor needs to sound an alarm
when the door has been left open for too long. In order to do this the TimedDoor object
communicates with another object called a Timer. (See Listing 2.) 

Listing 2

Timer
class Timer
{
public:
void Regsiter(int timeout, TimerClient* client);
};
class TimerClient
{
public:
virtual void TimeOut() = 0;
};

 When an object wishes to be informed about a timeout, it calls the Register function
of the Timer. The arguments of this function are the time of the timeout, and a pointer to a
TimerClient object whose TimeOut function will be called when the timeout expires.
How can we get the TimerClient class to communicate with the TimedDoor class so
that the code in the TimedDoor can be notified of the timeout? There are several alternatives.
Figure 1 shows a common solution. We force Door, and therefore TimedDoor, to
inherit from TimerClient. This ensures that TimerClient can register itself with the Timer
and receive the TimeOut message.


 Although this solution is common, it is not without problems. Chief among these is
that the Door class now depends upon TimerClient. Not all varieties of Door need timing.
Indeed, the original Door abstraction had nothing whatever to do with timing. If timingfree
derivatives of Door are created, those derivatives will have to provide nil implementations
for the TimeOut method. Moreover, the applications that use those derivatives will
have to #include the definition of the TimerClient class, even though it is not used.
Figure 1 shows a common syndrome of object oriented design in statically typed languates
like C++. This is the syndrome of interface pollution. The interface of Door has
been polluted with an interface that it does not require. It has been forced to incorporate
this interface solely for the benefit of one of its subclasses. If this practice is pursued, then
every time a derivative needs a new interface, that interface will be added to the base class.
This will further pollute the interface of the base class, making it “fat”.
Moreover, each time a new interface is added to the base class, that interface must be
implemented (or allowed to default) in derived classes. Indeed, an associated practice is to
add these interfaces to the base class as nil virtual functions rather than pure virtual functions;
specifically so that derived classes are not burdened with the need to implement
them. As we learned in the second article of this column, such a practice violates the
Liskov Substitution Principle (LSP), leading to maintenance and reusability problems.

Separate Clients mean Separate Interfaces. 

Door and TimerClient represent interfaces that are used by complely different clients.
Timer uses TimerClient, and classes that manipulate doors use Door. Since the clients are
separate, the interfaces should remain separate too. Why? Because, as we will see in the
next section, clients exert forces upon their server interfaces.

The backwards force applied by clients upon interfaces. 

When we think of forces that cause changes in software, we normally think about how
changes to interfaces will affect their users. For example, we would be concerned about
the changes to all the users of TimerClient, if the TimerClient interface changed. However,
there is a force that operates in the other direction. That is, sometimes it is the user that
forces a change to the interface.
For example, some users of Timer will register more than one timeout request. Consider
the TimedDoor. When it detects that the Door has been opened, it sends the Register
message to the Timer, requesting a timeout. However, before that timeout expires the door
closes; remains closed for awhile, and then opens again. This causes us to register a new
timeout request before the old one has expired. Finally, the first timeout request expires
and the TimeOut function of the TimedDoor is invoked. And the Door alarms falsely.
We can correct this situation by using the convention shown in Listing 3. We include a
unique timeOutId code in each timeout registration, and repeat that code in the TimeOut
call to the TimerClient. This allows each derivative of TimerClient to know which timeout
request is being responded to.

 Listing 3

Timer with ID
class Timer
{
public:
void Regsiter(int timeout,
int timeOutId,
TimerClient* client);
};
class TimerClient
{
public:
virtual void TimeOut(int timeOutId) = 0;
}; 

Clearly this change will affect all the users of TimerClient. We accept this since the
lack of the timeOutId is an oversight that needs correction. However, the design in Figure
1 will also cause Door, and all clients of Door to be affected (i.e. at least recompiled) by
this fix! Why should a bug in TimerClient have any affect on clients of Door derivatives
that do not require timing? It is this kind of strange interdependency that chills customers
and managers to the bone. When a change in one part of the program affects other completely
unerlated parts of the program, the cost and repercussions of changes become
unpredictable; and the risk of fallout from the change increases dramatically. 

But it’s just a recompile.

True. But recompiles can be very expensive for a number of reasons. First of all, they take
time. When recompiles take too much time, developers begin to take shortcuts. They may
hack a change in the “wrong” place, rather than engineer a change in the “right” place;
because the “right” place will force a huge recompilation. Secondly, a recompilation
means a new object module. In this day and age of dynamically linked libraries and incremental
loaders, generating more object modules than necessary can be a significant disadvantage.
The more DLLs that are affected by a change, the greater the problem of
distributing and managing the change. 

Content Courtesy: objectmentor.com
follow the link to read more and exmaple: http://www.objectmentor.com/resources/articles/isp.pdf 
 

Comments

Popular posts from this blog

Java XML getElementById returning null, fix using XPath

Sending an Email to Multiple Recipients - Cc: and Bcc:

Livares joins hands with ICT Academy of Kerala