Implementation Strategies for Interface Segregation Principle
Interface Segregation Principle (ISP)
Implementation Strategies
Interface Composition
One effective strategy for implementing the Interface Segregation Principle (ISP) is to use composition over inheritance. Composition involves building complex types by combining simpler ones, allowing for greater flexibility and reusability.
- Combining Interfaces: Instead of creating a single, large interface with multiple methods, break it down into smaller, more focused interfaces. Classes can then implement multiple smaller interfaces as needed, composing the required functionality.
- Example:
- Before (Inheritance):
- Java
- JavaScript
- Python
interface Machine {
void print(Document doc);
void scan(Document doc);
void fax(Document doc);
}
class MultiFunctionPrinter implements Machine {
@Override
public void print(Document doc) {
System.out.println("Printing document");
}
@Override
public void scan(Document doc) {
System.out.println("Scanning document");
}
@Override
public void fax(Document doc) {
System.out.println("Faxing document");
}
}
class Machine {
print(doc) {
throw new Error('This method should be overridden')
}
scan(doc) {
throw new Error('This method should be overridden')
}
fax(doc) {
throw new Error('This method should be overridden')
}
}
class MultiFunctionPrinter extends Machine {
print(doc) {
console.log('Printing document')
}
scan(doc) {
console.log('Scanning document')
}
fax(doc) {
console.log('Faxing document')
}
}
class Machine:
def print(self, doc):
raise NotImplementedError("This method should be overridden")
def scan(self, doc):
raise NotImplementedError("This method should be overridden")
def fax(self, doc):
raise NotImplementedError("This method should be overridden")
class MultiFunctionPrinter(Machine):
def print(self, doc):
print("Printing document")
def scan(self, doc):
print("Scanning document")
def fax(self, doc):
print("Faxing document")
- After (Composition):
- Java
- JavaScript
- Python
interface Printer {
void print(Document doc);
}
interface Scanner {
void scan(Document doc);
}
interface Fax {
void fax(Document doc);
}
class MultiFunctionPrinter implements Printer, Scanner, Fax {
@Override
public void print(Document doc) {
System.out.println("Printing document");
}
@Override
public void scan(Document doc) {
System.out.println("Scanning document");
}
@Override
public void fax(Document doc) {
System.out.println("Faxing document");
}
}
class Printer {
print(doc) {
console.log("Printing document");
}
}
class Scanner {
scan(doc) {
console.log("Scanning document");
}
}
class Fax {
fax(doc) {
console.log("Faxing document");
}
}
class MultiFunctionPrinter extends Printer, Scanner, Fax {
print(doc) {
console.log("Printing document");
}
scan(doc) {
console.log("Scanning document");
}
fax(doc) {
console.log("Faxing document");
}
}
class Printer:
def print(self, doc):
print("Printing document")
class Scanner:
def scan(self, doc):
print("Scanning document")
class Fax:
def fax(self, doc):
print("Faxing document")
class MultiFunctionPrinter(Printer, Scanner, Fax):
def print(self, doc):
print("Printing document")
def scan(self, doc):
print("Scanning document")
def fax(self, doc):
print("Faxing document")
In the composition approach, the MultiFunctionPrinter
class implements
multiple smaller interfaces, each representing a specific functionality. This
approach follows ISP by providing only the necessary methods for each client.
Role Interfaces
Role interfaces are another strategy for implementing ISP. A role interface represents a specific behavior or role that an object can perform. By defining role interfaces, you can create more flexible and reusable designs.
- Focused Interfaces: Each role interface defines a specific set of methods
related to a particular role. For example, instead of a general
Employee
interface, you might haveManager
andEngineer
interfaces, each with methods relevant to their roles. - Example:
- Java
- JavaScript
- Python
interface Manager {
void conductMeeting();
}
interface Engineer {
void developSoftware();
}
class TeamLead implements Manager, Engineer {
@Override
public void conductMeeting() {
System.out.println("Conducting a meeting");
}
@Override
public void developSoftware() {
System.out.println("Developing software");
}
}
class Manager {
conductMeeting() {
console.log("Conducting a meeting");
}
}
class Engineer {
developSoftware() {
console.log("Developing software");
}
}
class TeamLead extends Manager, Engineer {
conductMeeting() {
console.log("Conducting a meeting");
}
developSoftware() {
console.log("Developing software");
}
}
class Manager:
def conduct_meeting(self):
print("Conducting a meeting")
class Engineer:
def develop_software(self):
print("Developing software")
class TeamLead(Manager, Engineer):
def conduct_meeting(self):
print("Conducting a meeting")
def develop_software(self):
print("Developing software")
In this example, the TeamLead
class implements both Manager
and Engineer
role interfaces, providing the specific functionality required by each role.
This approach aligns with ISP by creating focused interfaces that represent
specific behaviors.
By utilizing interface composition and role interfaces, developers can effectively implement the Interface Segregation Principle, leading to more modular, maintainable, and flexible code.