Interface segregation principle
I — ISP— Interface segregation principle
Definitions
Many client-specific interfaces are better than one general-purpose interface.Keep interfaces small so that users don’t end up depending on things they don’t need.Clients should not be forced to depend upon interface that they do not use.larger interfaces should be split into smaller, more specific ones so that clients only need to know about the methods they actually require.Don’t show your clients more than they need to see.
ISP complements other SOLID principles like Single Responsibility Principle (SRP) and Liskov Substitution Principle (LSP). Adhering to ISP results in more flexible, maintainable, and testable software systems.
Let’s understand this with examples
/***
Violates ISP:
+ Robot is forced to implement eat() and sleep() even though it doesn't need them, violating ISP.
+ This creates unnecessary coupling and potential for errors.
*/
interface Worker {
void work();
void eat();
void sleep();
}
class Human implements Worker {
// ... implements all methods
}
class Robot implements Worker {
// ... implements work() but not eat() or sleep()
}
/***
Fix ISP:
+ Interfaces are now segregated based on functionality.
+ Robot only implements the methods it needs, reducing coupling.
+ Code becomes more flexible and maintainable.
*/
interface Workable {
void work();
}
interface Feedable {
void eat();
}
interface Restful {
void sleep();
}
class Human implements Workable, Feedable, Restful {
// ... implements all methods
}
class Robot implements Workable {
// ... implements only work()
}
Another example
/***
Issues:
+ This single interface encompasses methods for connecting, reading, writing, querying, updating, and deleting data.
+ Not all clients need all of these capabilities, leading to unnecessary coupling.
*/
interface DataSource {
void connect();
void disconnect();
void read();
void write();
void query();
void update();
void delete();
}
Applying ISP
/***
ISP fixes
+ Interfaces are now segregated based on specific data operations.
+ Classes implement only the interfaces required for their functionality.
+ For example:
+ A class for reading data from a file might implement Connectable and Readable.
+ A class for managing database transactions might implement Connectable, Queryable, Updatable, and Deletable.
*/
interface Connectable {
void connect();
void disconnect();
}
interface Readable {
void read();
}
interface Writable {
void write();
}
interface Queryable {
void query();
}
interface Updatable {
void update();
}
interface Deletable {
void delete();
}
ISP in functional programming:
In functional programming, ISP entails having concise functions, which also aligns with adhering to SRP. When a function is dedicated to a singular task, it is likely to be compact in size.
Guidelines for implementing the Interface Segregation Principle (ISP):
1. Identify Fat Interfaces:
- Look for interfaces with many methods, indicating potential for segregation.
- Analyze client usage patterns to determine which methods are not universally used.
- Consider the cohesion of methods within the interface. If they don’t relate to a single, well-defined responsibility, it’s a sign of potential segregation.
2. Split Interfaces Strategically:
- Break down large interfaces into smaller, more cohesive ones based on functionality.
- Group methods that logically belong together into separate interfaces.
- Aim for interfaces that represent distinct responsibilities or roles.
- Use clear and descriptive names for the new interfaces to communicate their purpose.
3. Favor Composition over Single Inheritance:
- Instead of a class implementing a single large interface, allow it to implement multiple smaller interfaces.
- This promotes flexibility and selective dependency management.
- Classes can choose only the interfaces that align with their specific behavior, reducing unnecessary coupling.
4. Refactor Existing Code:
- Apply ISP to existing codebases to improve design and reduce coupling.
- Identify “fat” interfaces and refactor them into smaller, more focused ones.
- Update client code to use the new, segregated interfaces appropriately.
- Consider using refactoring tools or techniques to streamline the process.
5. Consider Design Patterns:
- Certain design patterns can help achieve ISP compliance:
- Strategy Pattern: Encapsulate algorithms in interchangeable classes, allowing clients to select specific behaviors without depending on a large interface.
- Decorator Pattern: Add responsibilities to objects dynamically without modifying their core interfaces, promoting flexibility and adherence to ISP.
6. Test Thoroughly:
- After refactoring to apply ISP, conduct thorough testing to ensure functionality is preserved and dependencies are correctly managed.
- Write unit tests to verify the behavior of classes and interfaces in isolation.
- Perform integration tests to ensure proper interaction between components.
7. Balance with Other Principles:
- While ISP is valuable, consider it in conjunction with other SOLID principles:
- Single Responsibility Principle (SRP): Keep interfaces focused on a single responsibility to maintain cohesion.
- Liskov Substitution Principle (LSP): Ensure that subclasses can be used interchangeably with their superclasses, upholding contract consistency.
Remember:
- ISP is an ongoing practice, not a one-time fix.
- Reassess interfaces as code evolves to maintain adherence to the principle.
- By following these guidelines, you can create more flexible, maintainable, and testable software systems that adhere to the Interface Segregation Principle.