CSC/ECE 517 Fall 2012/ch2b 2w70 sm
Interface Segregation Principle
Introduction
The Interface Segregation Principle(also referred as ISP) states that "Clients should not be forced to implement interfaces they don't use". Instead of one fat interface many small interfaces are preferred based on groups of methods, each one serving one submodule.ISP focuses on the cohesiveness of interfaces with respect to the clients that use them.This principle alleviates the disadvantages of "fat" or "polluted" interfaces.The interface-segregation principle (ISP) is one of the five SOLID principles of Object-Oriented Design. ISP is very similar to high cohesion principle of GRASP.
Two main ISP guidelines are: Classes should not be forced to depend on methods that they do not use, and the dependency of one class to another one should depend on the smallest possible interface.
Motivation
When we design an application we should take care how we are going to abstract a module which contains several submodules. Considering the module implemented by a class, we can have an abstraction of the system done in an interface. But if we want to extend our application adding another module that contains only some of the submodules of the original system, we are forced to implement the full interface and to write some dummy methods. Such an interface is named fat interface or polluted interface. Having an interface pollution is not a good solution and might induce inappropriate behavior in the system.
Origin
The ISP was first used and formulated by Robert C. Martin when doing some consulting for Xerox. Xerox had created a new printer system that could perform a variety of tasks like stapling a set of printed papers and faxing. The software for this system was created from the ground up and performed its tasks successfully. As the software grew, making modification became more and more difficult so that even the smallest change would take a redeployment cycle to an hour. This was making it near impossible to continue development.
The main problem was that one main Job class was used by almost all of the tasks. Anytime a print job or a stapling job had to be done, a call was made to some method in the Job class.This means that the Job class was getting huge or 'fat', full of tons of different methods which were 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.So whenever a developer had to change a small detail about a print job, every one of the classes that used the Job class would have to be recompiled.
The solution suggested by Robert is called the Interface Segregation Principle today. His suggestion was that they add a layer of interfaces between the Job class and all of its clients. All of the dependencies could be reversed using the Dependency Inversion Principle.Instead of having one 'fat' 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 is created for each job, and the Job class would inherit from all of these interfaces. This segregation of interfaces vastly decoupled the software allowing the clients like the Staple class to only depend on the methods it cared about.So when a developer made a change to the Print Job, the Staple Job would be unaffected and have no need to recompile.
Violation
The Xerox example is a clear violation of the Interface Segregation Principle, but not all violations are so clear cut. A more commonly known example is the ATM Transaction example given in Agile Software Development: Principles, Patterns, and Practices and in an article also written by Robert C. Martin specifically about the ISP.This example is about an interface for the User Interface for an ATM, that handles all requests such as a deposit request, or a withdrawal request, and how this interface needs to be segregated into individual and more specific interfaces.
Explanation of Interface Segregation Principle in Object Oriented Programming
Figure 1 shows a class which has three clients and one "fat" interface to serve them all. Whenever a change is made to one of the methods that ClientA calls,ClientB and ClientC may be affected. This is the disadvantage in following this method. Instead of following this procedure,the methods needed by each client can be placed in special interfaces that are specific to that particular client.
Those interfaces are multiply inherited by the Service class, and implemented there.In this design,if the interface for ClientA needs to change, ClientB and ClientC will remain unaffected.This implementation is shown in figure 2 given below.
Examples
Example 1
Some interfaces are created with a good number of functionalities. But when those interfaces are implemented by client implementor classes, not required functionalities are forced to be implemented, and so the code will have many dummy/empty implementations. This situation can be prevented by seperating big interfaces into smaller ones. Only strictly related method definitions must be in the same interface. For a better design and usage,different types of functionalities must be placed in different interfaces. Below is a wrong design that's not using Interface Segregation Principle:
public interface Animal { void fly(); void run(); void bark(); } public class Bird implements Animal { public void bark() { /* do nothing */ } public void run() { // write code about running of the bird } public void fly() { // write code about flying of the bird } } public class Cat implements Animal { public void fly() { throw new Exception("Undefined cat property"); } public void bark() { throw new Exception("Undefined cat property"); } public void run() { // write code about running of the cat } } public class Dog implements Animal { public void fly() { } public void bark() { // write code about barking of the dog } public void run() { // write code about running of the dog } }
This above example uses interfaces where three different functions are defined but few are not being used.Below is the implementation which uses ISP:
public interface Flyable { void fly(); } public interface Runnable { void run(); } public interface Barkable { void bark(); } public class Bird implements Flyable, Runnable { public void run() { // write code about running of the bird } public void fly() { // write code about flying of the bird } } public class Cat implements Runnable{ public void run() { // write code about running of the cat } } public class Dog implements Runnable, Barkable { public void bark() { // write code about barking of the dog } public void run() { // write code about running of the dog } }
Example 2
The following code violates ISP shows the outline of three classes:
public class Contact { public string Name { get; set; } public string Address { get; set; } public string EmailAddress { get; set; } public string Telephone { get; set; } } public class Emailer { public void SendMessage(Contact contact, string subject, string body) { // Code to send email, using contact's email address and name } } public class Dialler { public void MakeCall(Contact contact) { // Code to dial telephone number of contact } }
The Contact class represents a person or business that can be contacted. The class holds the person's name, address, email address and telephone number. The Emailer class sends email messages to contacts. The contact and the subject and body of the email are passed to the parameters. The Dialler class extracts the telephone number from the Contact and calls it using an automatic dialling system. The Emailer class is a client of the Contact class. Although it only requires access to the Name and EmailAddress properties, it is aware of other members too. Similarly, the Dialler class uses a single property, "Telephone". However, it has access to the entire Contact interface. To refactor the code to comply with the ISP we need to hide unused members from the client classes. We can achieve this by introducing two new interfaces, both implemented by Contact. The IEmailable interface defines properties that hold the name and email address of an object that can receive email. The IDiallable interface includes only a Telephone property, which is enough to allow client classes to call the telephone number of a target object.
The Email class is updated, replacing the Contact dependency with an IEmailable object. Similarly, the Dialler's dependency becomes an IDiallable instance. Both classes now interact with contacts using the smallest possible interface.
With smaller interfaces it is easier to introduce new classes that implement them. To demonstrate, the refactored code includes a new class named "MobileEngineer". This represents engineers that visit customer sites. Engineer has properties for a name, telephone number and vehicle registration. The class implements IDiallable so that the Dialler object can call engineers.
public interface IEmailable { string Name { get; set; } string EmailAddress { get; set; } } public interface IDiallable { string Telephone { get; set; } } public class Contact : IEmailable, IDiallable { public string Name { get; set; } public string Address { get; set; } public string EmailAddress { get; set; } public string Telephone { get; set; } } public class MobileEngineer : IDiallable { public string Name { get; set; } public string Vehicle { get; set; } public string Telephone { get; set; } } public class Emailer { public void SendMessage(IEmailable target, string subject, string body) { // Code to send email, using target's email address and name } } public class Dialler { public void MakeCall(IDiallable target) { // Code to dial telephone number of target } }
The ATM User Interface Example
Consider a traditional Automated Teller Machine (ATM) problem. The user interface of an ATM machine needs to be very flexible.The output may need to be translated into many different languages. It may need to be presented on a screen, or on a braille tablet, or spoken out a speech synthesizer.This can be achieved by creating an abstract base class that has pure virtual functions for all the different messages that need to be presented by the interface.
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 { ... }
Also consider that each different transaction that the ATM can perform is encasulated as a derivative of the class Transaction. So, we might have classes such as DepositTransaction,WithdrawlTransaction, TransferTransaction, etc. Each of these objects issues message to the UI. For example, the DepositTransaction object calls the RequestDepositAmount member function of the UI class. Whereas the TransferTransaction object calls the RequestTransferAmount member function of UI. This corresponds to the diagram in Figure 6.
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); } }
This is precicely 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 coresponding change to the UI, thereby affecting all the other derivatives of Transaction, and every other class that depends upon the UI interface. This unfortunate coupling can be avoided by segregating the UI interface into induvidual abstract base classes such as DepositUI, WithdrawUI and TransferUI. These abstract base classes can then be multiply inherited into the final UI abstract class. Figure 7 show this model.
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 { ... }
Related Concepts
The following concepts are related to this one:
- Low coupling-- Coupling refers to the relationship of a module with another module. A module is said to be highly coupled with another module if changes to it will result to changes to the other module. And a module is said to be loosely coupled if a module is independent of any other modules. This can be achieved by having a stable interface that effectively hides the implementation of another module.
Benefits of low coupling are:
1.maintainability – changes are confined in a single module
2.testability – modules involved in unit testing can be limited to a minimum
3.readability – classes that need to be analyzed are kept at a minimum
- High cohesion--Cohesion refers to the measure of how strongly-related the functions of a module are. Low cohesion refers to modules that have different unrelated responsibilities. High cohesion refers to modules that have functions that are similar in many aspects.
Benefits of high cohesion are:
1.Readability – (closely) related functions are contained in a single module
2.Maintainability – debugging tends to be contained in a single module
3.Reusability – classes that have concentrated functionalities are not polluted with useless functions
- Liskov Substitution Principle (LSP)--Liskov Substitution principle (LSP) states that "Methods that use references to the base classes must be able to use the objects of the derived classes without knowing it".This principle was written by Barbara Liskov in 1988.The idea here is that the subtypes must be replaceable for the super type references without affecting the program execution.
Advantages
- Reduces coupling within the system.
- Reduces unwanted side effects
- Reusability is increased
Disadvantages
If the design is already done fat interfaces can be segregated using the Adapter pattern.Like every principle Interface Segregation Principle is one principle which require additional time and effort spent to apply it during the design time and increase the complexity of code. But it produce a flexible design. If we are going to apply it more than is necessary it will result a code containing a lot of interfaces with single methods, so applying should be done based on experience and common sense in identifying the areas where extension of code are more likely to happens in the future.
Conclusion
ISP is a software development principle used for clean development and intends to make software easy-to-change.ISP helps developers to change, refactor and redeploy their code easily.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. In a nutshell, no client should be forced to depend on methods it does not use.
References
http://codebalance.blogspot.com/2010/09/oop-solid-rules-interface-segregation.html
http://en.wikipedia.org/wiki/Interface_segregation_principle
http://blog.sanaulla.info/2011/11/28/solid-liskov-substitution-principle/
http://www.blackwasp.co.uk/ISP.aspx
http://en.wikipedia.org/wiki/User:LupusDei108/Interface_Segregation_Principle
http://www.oodesign.com/interface-segregation-principle.html
http://www.objectmentor.com/resources/articles/Principles_and_Patterns.pdf
http://www.objectmentor.com/resources/articles/isp.pdf
http://stefanroock.files.wordpress.com/2011/09/solidforyourlanguage.pdf