Keeping things (interfaces) simple
Premise
Abstraction is the core of Object Oriented Design paradigm. Abstraction in software development involves simplifying complex systems by highlighting essential details while hiding unnecessary complexities. It allows us to focus on relevant features, promoting clarity and maintainability. Abstraction provides a high-level view, enabling effective problem-solving without delving into intricate implementation details.
In Java, it is implemented with the help of interfaces
and abstract classes
. Interface
defines a set of abstractions that the implementing classes must implement. Similarly, abstract class
is one one which has at least one unimplemented method. Such classes cannot be instantiated, and its subclasses are responsible for providing the implementations.
In this article, we will talk about the Interface Segregation Principle (ISP) and understand how it encourages us to break one big set of features into small manageable sized bits which we can manage better.
Formal Defnition
This principle states
a client should not be forced to implement interfaces it does not use.
Let us delve deeper into this concept with the help of an example (also available here). Suppose we have an interface named Tasks
which is to implemented by Assignee
class (and its subclasses). There could be many types of Assignee
's but let's just focus on two roles - Developer
and Tester
.
// Interface Tasks
interface Tasks {
void code();
void test();
}
// Class Assignee
abstract class Assignee implements Tasks {}
// Developer is an Assingee
class Developer extends Assignee {
@Override
public void code() { System.out.println("I can code!"); }
@Override
public void test() {
System.out.println("throw new UnsupportedOperationException('I can\'t test!')"); }
}
// Tester is an Assingee
class Tester extends Assignee {
@Override
public void code() {
System.out.println("throw new UnsupportedOperationException('I can\'t code!');"); }
@Override
public void test() { System.out.println("I can test!"); }
}
Clearly as we see above, the Tasks
interface is overdone (enforces a lot of guaruntees) and it unnecessarily forces the subclasses of Assingee
class to implement methods which they naturally shouldn't (test
method in Developer
and code
method in Tester
). So what should be the solution?
Let us try to further breakdown the abstract methods of Test
interface into more manageable abstractions (namely) - Dev
and QA
.
With this change in place, now we can ensure that the class Developer
doesn't have to care about test
method and it can only focus on code
method. Likewise, the Tester
class doesn't have to worry about providing for code
method, and just focus on providing implementation of test
method only. Furthermore, if more subtypes of Assignee
are added in the system tomorrow, we can pick and choose the abstractions they are designated for. For example, ProjectLead
can implement both Dev
and QA
interface to be responsible for the larger role. Let us see this example with the help of the code snippet given below:
// class Assignee
abstract class Assignee {}
// Interface Dev for Developer tasks
interface Dev { void code(); }
// Interface QA for Test tasks
interface QA { void test(); }
// Developer is an Assingee
class Developer extends Assignee implements Dev {
@Override
public void code() { System.out.println("Developer can code!"); }
}
// Tester is an Assingee
class Tester extends Assignee implements QA {
@Override
public void test() { System.out.println("Tester can test!"); }
}
// ProjectLead is an Assignee
class ProjectLead extends Assignee implements Dev, QA {
@Override
public void code() { System.out.println("ProjectLead can code!"); }
@Override
public void test() { System.out.println("ProjectLead can test!"); }
}
Now, each class implements only the interface relevant to its responsibilities, adhering to the Interface Segregation Principle.
Advantages
Simplicity
Code becomes more straightforward and focused
Flexibility
Classes are not burdened with irrelevant methods, making the codebase more adaptable
Disadvantages
Interface Proliferation
Introducing more interfaces may be necessary, potentially leading to a larger number of small interfaces
Summary
The Interface Segregation Principle encourages us to be mindful of the specific needs of our classes when designing interfaces. By creating client-specific interfaces, we enhance code clarity, simplicity, and adaptability. Embrace ISP, and let your interfaces be as nimble and purposeful as specific tools your tool-box!