4. Interface Segregation Principle (ISP)

Introduction

Complex interfaces can become a burden in software development, forcing classes to implement methods they don’t use. The Interface Segregation Principle (ISP), a critical aspect of the SOLID principles, advocates for designing smaller, more specific interfaces. This approach ensures that implementing classes only need to be concerned with the methods that are relevant to them, thus enhancing code modularity and clarity.

Understanding the Interface Segregation Principle

Lets use electrical cables and connectors as an example for the Interface Segregation Principle (ISP) in software development. On one side, each cable has its own plug and on the other side, the tangled cables converging into a single point. What do you think is better? The tangled wires or the connectors that can be plugged whenever necessary? Obviously, the untangled wires are favorable due to the simplicity and flexibility.  This visual representation reinforces the importance of designing specific, streamlined interfaces by applying ISP principle in our software applications to improve their flexibility and maintainability. 

ISP promotes the splitting of large interfaces into smaller and more specific ones so that clients will only have to know about the methods that are of interest to them. This not only prevents the interfaces from becoming bloated but also enhances class cohesion and encapsulation. In simple words its mantra is –

No client should be forced to depend on methods it does not use.

Why is ISP Important?

    • Reduces Interface Bloat: Ensures interfaces remain streamlined and relevant to the clients that use them.

    • Increases System Modularity: Facilitates better organization of code and makes the system easier to navigate and maintain.

    • Enhances Flexibility: Smaller interfaces are easier to manage, adapt, and evolve as the system grows.

ISP in Action:

Multifunction printer (Java Example)

Consider an office automation system where a multifunction printer supports operations like printing, scanning, and faxing.

Without ISP:

interface Machine {
void print(Document d);
void scan(Document d);
void fax(Document d);
}

class MultiFunctionPrinter implements Machine {
public void print(Document d) { /* Implementation */ }
public void scan(Document d) { /* Implementation */ }
public void fax(Document d) { /* Implementation */ }
}
class SimplePrinter implements Machine {
public void print(Document d) { /* Implementation */ }
public void scan(Document d) {
throw new UnsupportedOperationException("Scan not supported.");
}
public void fax(Document d) {
throw new UnsupportedOperationException("Fax not supported.");
}
}

Impact of Violation:

    • Unnecessary Implementation Burden: The SimplePrinter is forced to implement scan and fax methods even though it does not need them. This leads to cluttered and potentially error-prone code, especially when exceptions are used to handle unsupported operations.

    • Increased Complexity: Managing and extending the Machine interface becomes cumbersome as it grows with more functionalities, affecting all implementing classes regardless of whether they use those functionalities.

With ISP:

interface Printer {
void print(Document d);
}

interface Scanner {
void scan(Document d);
}
interface Fax {
void fax(Document d);
}
class MultiFunctionPrinter implements Printer, Scanner, Fax {
public void print(Document d) { /* Implementation */ }
public void scan(Document d) { /* Implementation */ }
public void fax(Document d) { /* Implementation */ }
}
class SimplePrinter implements Printer {
public void print(Document d) { /* Implementation */ }
}

Impact of Adhering to ISP:

    • Simplified Interfaces: Each device class implements only the interfaces relevant to its functionality. SimplePrinter no longer needs to deal with scanning and faxing, leading to cleaner, more maintainable code.

    • Reduced Risk of Errors: Since SimplePrinter only implements Printer, there is no need for dummy implementations or throwing exceptions for unsupported operations, reducing the risk of runtime errors.

    • Easier Maintenance and Scalability: The system is easier to maintain and extend. New functionalities, like adding a new type of printer or a new function (e.g., duplex printing), can be integrated by creating new interfaces or extending existing ones without affecting old classes.

Content management system (Python Example)

Let’s look at a content management system where different user types have different content operations like create, edit, delete and read content.

Without ISP:

class ContentManager:
def create(content):
pass
def edit(content):
pass
def delete(content):
pass
def read(content):
pass


class Admin(ContentManager):
# Implements all methods
pass

class Guest(ContentManager):
def create(content):
raise NotImplementedError
def edit(content):
raise NotImplementedError
def delete(content):
raise NotImplementedError
# Only uses read
def read(content):
pass

Impact of Violation:

    • Forced Implementations: Guests, who only need to read content, are forced to implement methods for creating, editing, and deleting, which they do not use. This results in unnecessary code, potential for errors, and a violation of the single-responsibility principle.

    • Increased Complexity: As the system grows, managing such bloated interfaces becomes cumbersome, and the risk of introducing bugs when modifying one method is high because it could affect classes that implement methods they don’t need.

With ISP:

class Readable:
def read(content):
pass


class Editable:
def edit(content):
pass

class Deletable:
def delete(content):
pass

class Creatable:
def create(content):
pass

class Admin(Creatable, Editable, Deletable, Readable):
# Implements all interfaces
pass

class Guest(Readable):
# Implements only the Readable interface
pass

Impact of Adhering to ISP:

    • Reduced Interface Bloat: Each class only implements the interfaces that pertain to its functionality. For example, the Guest class implements only the Readable interface, aligning closely with its responsibilities.

    • Easier Maintenance and Extension: It’s simpler to maintain and extend the code since changes to an interface affect only the classes that actually use that interface. This isolation reduces the risk of bugs significantly.

    • Increased Flexibility and Scalability: Adding new roles or functionalities becomes straightforward without impacting existing code, promoting better scalability and flexibility.

Conclusion

The Interface Segregation Principle guides developers to design interfaces that are specific and targeted to the needs of the client modules, reducing the burden of unnecessary implementation. By following ISP, developers can create systems that are easier to extend and maintain, more flexible to evolve, and less prone to bugs.

Consider how the Interface Segregation Principle could streamline interfaces in your current projects. Do you see areas where separating interfaces could reduce complexity? Share your thoughts or experiences in applying ISP in your development work.