CSC/ECE 517 Fall 2012/ch2b 2w70 sm
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.
It 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.Such shrunken interfaces are also called role interfaces.
Origin/Motivation
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 design 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 resulted in a huge or '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 is what is called the Interface Segregation Principle today. Applied to the Xerox software, a layer of interfaces between the Job class and all of 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, which were all implemented by the Job class.
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 [1] and in an article also written by Robert C. Martin specifically about the ISP.[5] 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.
Examples
Example 1
Some interfaces are created with huge amount 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 } }
Example2
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 demonstrate the application of the ISP, we can review some code that violates it and explain how to refactor to comply with the principle. The following code 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 example code violates the ISP. 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 } }
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.
<t>Benefits of low coupling are:<\t>
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.
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/