CSC/ECE 517 Fall 2007/wiki3 4 sa: Difference between revisions
No edit summary |
No edit summary |
||
Line 15: | Line 15: | ||
:"The dependency of one class to another one should depend on the smallest possible interface" | :"The dependency of one class to another one should depend on the smallest possible interface" | ||
:"Make fine grained interfaces that are client specific." | :"Make fine grained interfaces that are client specific." | ||
While it can be argued that having a class that has many interfaces violates the Single Responsibility Principle [http://c2.com/cgi/wiki?SingleResponsibilityPrinciple SRP], there are many instances where having a class implementing different interfaces makes sense, e.g., a graphics class might need to be rendered to screen or serialized to a file. | |||
= Examples = | |||
The following two examples are taken from Martin's paper, but have been ported to Java. | |||
== Security system == | |||
Suppose you have a security system that has Door objects: | |||
<pre> | |||
interface Door { | |||
public void Lock(); | |||
public void Unlock(); | |||
public boolean IsDoorOpen(); | |||
}; | |||
</pre> | |||
Now suppose you have a TimedDoor which needs to sound an Alarm when the door has been left open for too long. To implement this functionality the TimerDoor object communicates with a Timer object: | |||
<pre> | |||
public class Timer | |||
{ | |||
... | |||
public void Register(int timeout, TimerClient* client) { | |||
... | |||
} | |||
... | |||
}; | |||
interface TimerClient | |||
{ | |||
public void TimeOut(); | |||
}; | |||
</pre> | |||
We might be tempted to do the following: | |||
<pre> | |||
interface Door extends TimerClient { | |||
public void Lock(); | |||
public void Unlock(); | |||
public boolean IsDoorOpen(); | |||
}; | |||
class TimedDoor implements Door { | |||
... | |||
} | |||
</pre> | |||
The Door interface has been polluted with the Timer interface in order to provide the Timer interface to TimedDoor and all Door clients now also depend on TimerClient. To void this we can do the following instead: | |||
<pre> | |||
// Unchanged from first listing | |||
interface Door { | |||
public void Lock(); | |||
public void Unlock(); | |||
public boolean IsDoorOpen(); | |||
}; | |||
class TimedDoor implements Door, TimerClient { | |||
... | |||
} | |||
</pre> | |||
This conforms to ISP by providing the required separate interfaces. | |||
== ATM System == | |||
Consider an ATM that needs to be very flexible and support screen, braille tablet and speech synthesizer user interfaces. We can abstract the main operations and use an interface which would be implemented by specific UI classes: | |||
<pre> | |||
interface ATM_UI { | |||
void deposit(double amt, double account); | |||
void withdraw(double amt, double account); | |||
void transfer(double amt, double fromAccount, double toAccount); | |||
} | |||
public class BrailleUI implements ATM_UI { | |||
... | |||
} | |||
public class ScreenUI implements ATM_UI { | |||
... | |||
} | |||
public class SpeechUI implements ATM_UI { | |||
... | |||
} | |||
</pre> | |||
Now consider also that each different transaction the ATM can perform are encapsulated in a Transaction object: | |||
<pre> | |||
interface Transaction { | |||
void execute(); | |||
} | |||
class DepositTransaction implements Transaction { | |||
void execute() { | |||
atm.deposit(amt, acct); | |||
} | |||
} | |||
class WithdrawalTransaction implements Transaction { | |||
void execute() { | |||
atm.withdraw(amt, acct); | |||
} | |||
} | |||
class TransferTransaction implements Transaction { | |||
void execute() { | |||
atm.withdraw(amt, fromAcct, toAcct); | |||
} | |||
} | |||
</pre> | |||
Notice that this is precisely the situation that the ISP tells us to avoid. Each of the transactions is using a portion of the UI that no other object uses. This creates the possibility that changes to one of the derivatives of Transaction will force corresponding change to the UI, thereby affecting all the other derivatives of Transaction, and every other class that depends upon the UI interface. To fix the problem we can do the following instead: | |||
<pre> | |||
interface DepositUI { | |||
void deposit(double amt, double account); | |||
} | |||
interface WithdrawalUI { | |||
void withdraw(double amt, double account); | |||
} | |||
interface TransferUI { | |||
void transfer(double amt, double fromAccount, double toAccount); | |||
} | |||
interface ATM_UI extends DepositUI, WithdrawalUI, TransferUI { | |||
... | |||
} | |||
</pre> | |||
And have the individual Transaction objects only reference the needed interfaces. | |||
= Web Resources = | = Web Resources = |
Revision as of 21:59, 28 November 2007
Take the Interface Segregation principle and catalog the information on it available on the Web. We didn't cover it in class, but you can look it up on the Web or in the ACM DL. Find good descriptions and good, concise, understandable examples. Tell which you consider the best to present to a class.
Introduction
All the basic design principles converge on the use of abstraction for incorporating flexibility using loose coupling behavior. The Interface Segregation Principle (ISP) provides guidelines for designing interfaces to classes that must provide different services to different clients. The Interface Segregation Principle focuses on the cohesiveness of interfaces with respect to the clients that use them.
Definition
ISP was originally presented in a paper by Robert C. Martin in his Engineering Notebook column for the C++ Report. The paper is still available at the Object Mentor website (ISP).
The essence of Integration Segregation Principle is that "Clients should not be forced to depend upon interfaces that they don’t use."
Some other definitions of ISP are:
- "Many client specific interfaces are better than one general purpose interface"
- "The dependency of one class to another one should depend on the smallest possible interface"
- "Make fine grained interfaces that are client specific."
While it can be argued that having a class that has many interfaces violates the Single Responsibility Principle SRP, there are many instances where having a class implementing different interfaces makes sense, e.g., a graphics class might need to be rendered to screen or serialized to a file.
Examples
The following two examples are taken from Martin's paper, but have been ported to Java.
Security system
Suppose you have a security system that has Door objects:
interface Door { public void Lock(); public void Unlock(); public boolean IsDoorOpen(); };
Now suppose you have a TimedDoor which needs to sound an Alarm when the door has been left open for too long. To implement this functionality the TimerDoor object communicates with a Timer object:
public class Timer { ... public void Register(int timeout, TimerClient* client) { ... } ... }; interface TimerClient { public void TimeOut(); };
We might be tempted to do the following:
interface Door extends TimerClient { public void Lock(); public void Unlock(); public boolean IsDoorOpen(); }; class TimedDoor implements Door { ... }
The Door interface has been polluted with the Timer interface in order to provide the Timer interface to TimedDoor and all Door clients now also depend on TimerClient. To void this we can do the following instead:
// Unchanged from first listing interface Door { public void Lock(); public void Unlock(); public boolean IsDoorOpen(); }; class TimedDoor implements Door, TimerClient { ... }
This conforms to ISP by providing the required separate interfaces.
ATM System
Consider an ATM that needs to be very flexible and support screen, braille tablet and speech synthesizer user interfaces. We can abstract the main operations and use an interface which would be implemented by specific UI classes:
interface ATM_UI { void deposit(double amt, double account); void withdraw(double amt, double account); void transfer(double amt, double fromAccount, double toAccount); } public class BrailleUI implements ATM_UI { ... } public class ScreenUI implements ATM_UI { ... } public class SpeechUI implements ATM_UI { ... }
Now consider also that each different transaction the ATM can perform are encapsulated in a Transaction object:
interface Transaction { void execute(); } class DepositTransaction implements Transaction { void execute() { atm.deposit(amt, acct); } } class WithdrawalTransaction implements Transaction { void execute() { atm.withdraw(amt, acct); } } class TransferTransaction implements Transaction { void execute() { atm.withdraw(amt, fromAcct, toAcct); } }
Notice that this is precisely the situation that the ISP tells us to avoid. Each of the transactions is using a portion of the UI that no other object uses. This creates the possibility that changes to one of the derivatives of Transaction will force corresponding change to the UI, thereby affecting all the other derivatives of Transaction, and every other class that depends upon the UI interface. To fix the problem we can do the following instead:
interface DepositUI { void deposit(double amt, double account); } interface WithdrawalUI { void withdraw(double amt, double account); } interface TransferUI { void transfer(double amt, double fromAccount, double toAccount); } interface ATM_UI extends DepositUI, WithdrawalUI, TransferUI { ... }
And have the individual Transaction objects only reference the needed interfaces.
Web Resources
We were unable to find many examples of ISP in the web, the reason might be that ISP is very well explained in the original paper (ISP), which has two examples in C++ and an early version of UML. These examples use C++'s multiple inheritance due to the lack of real interfaces in the language. Each interface is defined using an abstract class with pure virtual methods. Even when these examples are very good, the use of C++ and the old version of UML might make it harder for people used to languages with support for real interfaces like Java or C# to understand them.
The ISP page at Doodle Project has an example in modern UML [1].
A Java Boutique page is available with a very concise and clear example in UML [2].
The following page contains an interesting example because it relates to real-world objects, in this case a cell phone [3].
The Object Mentor website also provides a paper with a number of design principles summarized, including ISP [4]. The advantage of said paper is that the other principles are easily accessible and a designer can compare for advantages and trade-off where multiple principles may be applied.
Still the best examples in our opinion are the ones found on the original paper by Martin.
Conclusion
Interface Segregation Principle's idea is to see that if there are two non-cohesive functionalities, keep them separate, so that they can be used independent of other. This avoids design of fat interfaces, and provides a clear design to the user (client). Break the functionalities into atomic interfaces that can be then individually accessed by the user.
Further reading
Agile Software Development, Principles, Patterns, and Practices
Agile Principles, Patterns, and Practices in C#
Note: Santosh Gurijala (skgurija) and Agustin Vega-Frias (jvegafr) are the authors of this page.