DEV Community

Cover image for I - Interface Segregation Principle (ISP)
Nozibul Islam
Nozibul Islam

Posted on

I - Interface Segregation Principle (ISP)

What is Interface Segregation Principle(ISP) ?

The Interface Segregation Principle (ISP) is one of the SOLID principles in object-oriented programming, which focuses on designing interfaces so that no class is forced to implement methods it doesn't need.

In simple terms, ISP suggests that instead of creating large, all-encompassing interfaces, we should design smaller, more focused ones. This ensures that each class only implements the methods it actually requires.

For example:

If a large interface contains many functions, but a class doesn’t need all of those functions, it would still have to implement all of them—even if some are unnecessary. ISP suggests that we should split such large interfaces into smaller, more focused ones. This way, each class can implement only the functions it actually needs and avoid implementing unnecessary ones.

By following this approach, code complexity is reduced, making it easier to understand and maintain.

The main goals of ISP are:

  • Breaking down large and complex interfaces into smaller, more specific ones.

  • Ensuring that a class doesn’t need to implement unnecessary functionality.

  • Avoiding placing undue responsibility on a class, resulting in cleaner and more understandable code.

For example:

If an interface has 10 methods, but a specific class only needs 2 of them, ISP recommends splitting this large interface. This way, each class can implement only the methods it requires, without having to implement the others.

Example 1:

Let’s say we have a Worker interface that is used for all types of tasks:

Java code:

interface Worker {
    void work();
    void eat();
}
Enter fullscreen mode Exit fullscreen mode

Now, there are two classes: HumanWorker and RobotWorker. HumanWorker can both eat and work, but RobotWorker cannot eat. Still, RobotWorker has to implement the eat() method, which violates ISP:

Java code:

class HumanWorker implements Worker {
    public void work() {
        System.out.println("Human is working");
    }
    public void eat() {
        System.out.println("Human is eating");
    }
}

class RobotWorker implements Worker {
    public void work() {
        System.out.println("Robot is working");
    }
    public void eat() {
        // Robot can't eat, but still needs to implement this method
    }
}
Enter fullscreen mode Exit fullscreen mode

We can solve this problem using ISP by creating separate interfaces for working Workable and eating Eatable:

Java code:

interface Workable {
    void work();
}

interface Eatable {
    void eat();
}

class HumanWorker implements Workable, Eatable {
    public void work() {
        System.out.println("Human is working");
    }
    public void eat() {
        System.out.println("Human is eating");
    }
}

class RobotWorker implements Workable {
    public void work() {
        System.out.println("Robot is working");
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, RobotWorker no longer needs to implement the unnecessary eat() method, adhering to the Interface Segregation Principle (ISP).

Example 1:

Let's say there is a machine interface that can both run and recharge:

JavaScript code:

class Machine {
  run() {
    console.log("Machine is running");
  }

  recharge() {
    console.log("Machine is recharging");
  }
}
Enter fullscreen mode Exit fullscreen mode

However, some machines can only run but cannot recharge. According to ISP, we should separate the responsibility of recharging into a different interface:

JavaScript code:

class RunnableMachine {
  run() {
    console.log("Machine is running");
  }
}

class RechargeableMachine {
  recharge() {
    console.log("Machine is recharging");
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, machines that don’t need to recharge only implement the run() method, while rechargeable machines implement the recharge() method. This separation follows the Interface Segregation Principle (ISP).

Example 2:

Let’s say there is a Printer class that can both print and scan:

JavaScript code:

class Printer {
  print() {
    console.log("Printing...");
  }

  scan() {
    console.log("Scanning...");
  }
}
Enter fullscreen mode Exit fullscreen mode

However, not all printers have the ability to scan. In this case, we can separate the necessary methods into different interfaces:

JavaScript code:

class PrintOnly {
  print() {
    console.log("Printing...");
  }
}

class ScanAndPrint {
  print() {
    console.log("Printing...");
  }

  scan() {
    console.log("Scanning...");
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, printers that only need the print functionality will implement the PrintOnly class, while those that require both printing and scanning will implement the ScanAndPrint class. This design adheres to the Interface Segregation Principle (ISP), ensuring that each class only implements what it truly needs.

Example 3:

Let’s say we have a Vehicle class that can both drive and fly:

JavaScript code:

class Vehicle {
  drive() {
    console.log("Driving...");
  }

  fly() {
    console.log("Flying...");
  }
}
Enter fullscreen mode Exit fullscreen mode

However, not all vehicles can fly. To solve this, we can create separate interfaces:

JavaScript code:

class DriveOnly {
  drive() {
    console.log("Driving...");
  }
}

class FlyAndDrive {
  drive() {
    console.log("Driving...");
  }

  fly() {
    console.log("Flying...");
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, vehicles that can only drive will implement the DriveOnly class, while vehicles that can both drive and fly will implement the FlyAndDrive class. This solution follows the Interface Segregation Principle (ISP), ensuring that classes only implement the functionality they need.

Importance and Real-Life Applications of ISP:

  1. Improves Code Maintainability: ISP ensures that classes are only required to implement the methods they need. This makes the code easier to maintain since classes aren’t cluttered with unnecessary methods.

  2. Use of Specific Interfaces: By using smaller, more focused interfaces instead of large, generalized ones, development becomes more efficient, as there’s no need to deal with unnecessary functionality.

  3. Real-Life Solution: Imagine you are working with different types of devices such as printers, scanners, and multifunction devices. Each device has its own specific set of tasks. Using ISP, you can create separate interfaces for each task (e.g., printing, scanning) so that each device only implements the functionalities it requires. This keeps the code clean and well-organized.

When to Use ISP:

  • When multiple classes have different requirements, instead of using a large, generalized interface, you should break it into smaller, more specific interfaces.

  • If you notice that a class is forced to implement methods that it doesn’t need or use, you can apply ISP to ensure the class only implements relevant functionality.

Problems Caused by Violating ISP:

  1. Unnecessary Method Implementation: When a class implements a large interface but does not use all the methods, it is forced to implement unnecessary methods. This leads to extraneous methods in the code that are not needed.

  2. Increased Code Complexity: Large interfaces can result in excessive responsibilities for classes, making the code unnecessarily complex. This complexity makes it difficult to maintain the code, and introducing new changes can become risky.

  3. Violation of Class Responsibilities: When ISP is violated, a class may have to implement methods that are not directly related to its core functionality. This also violates the Single Responsibility Principle (SRP), as the class becomes involved in tasks outside its primary role.

  4. Maintenance and Update Issues: When changes are made to a large interface, all classes that implement that interface must adjust to those changes. If smaller interfaces were used, only the relevant classes would need updates, making it easier to maintain consistency. Maintaining such consistency with large interfaces can become challenging.

  5. Decreased Code Reusability: Large interfaces force all classes to implement all methods, leading to decreased reusability. Each class may end up containing unnecessary code, which reduces the overall reusability of the code.

Example:

Suppose you have a large interface called Worker, which includes work() and eat() methods. Now, for a robot, there is no need for the eat() method, but the robot class is still required to implement it. This violates ISP and results in unnecessary methods that are irrelevant to the robot’s functionality.

Conclusion:

Thus, violating ISP leads to increased code complexity, makes maintenance difficult, and forces unnecessary method implementations.

Interface Segregation Principle(ISP) in React

The Interface Segregation Principle (ISP) simply states that an object or component should not be forced to implement methods that it does not use. Each component should be given methods or props that are relevant to its specific needs.

The core idea of ISP is that clients should not be provided with interfaces or APIs that they do not need to use. In simpler terms, it suggests breaking down large interfaces or classes into smaller, more focused ones, allowing clients to use only the parts that are necessary for them.

This approach promotes cleaner, more maintainable code and enhances the flexibility of the system by ensuring that each component interacts only with the functionalities it requires.

Simple Example:

Imagine a restaurant that has three types of customers: 1) those who come to eat rice, 2) those who come to eat pasta, and 3) those who come to eat salad. If we provide them all with the same menu that includes everything together, many items will be irrelevant to some customers. This would make the menu unnecessarily complicated for them.

According to the Interface Segregation Principle (ISP), the customer who comes for rice should only be given the rice menu, the pasta eater should only receive the pasta menu, and the salad eater should only get the salad menu. This way, the experience is simplified for everyone, allowing each customer to focus on what they actually want without any unnecessary options.

This analogy illustrates how ISP encourages tailoring interfaces to meet specific needs, making interactions more straightforward and efficient.

ISP in React Simplified:

In React, we often create large components that contain many props or methods. However, it's common for a component not to need all of those props. According to the Interface Segregation Principle (ISP), components should be broken down into smaller parts so that each component only receives the props and methods that are necessary for its functionality.

By following this principle, you can achieve:

  1. Cleaner Code: Each component remains focused on its specific task, making the codebase easier to understand and maintain.

  2. Improved Reusability: Smaller components can be reused in different contexts without carrying unnecessary props.

  3. Better Performance: Since components only receive what they need, rendering becomes more efficient.

For example, instead of a large UserProfile component that handles both user information and user settings, you could create two separate components: UserInfo and UserSettings. Each component would only receive the relevant props, following the ISP and resulting in a more modular and maintainable structure.

Example 1:

Imagine we have created a large Button component that can perform various actions such as onClick, onHover, onFocus, and more. However, in some cases, we might only need the onClick functionality, but the other functions also come with the component, which we don’t need.

According to the Interface Segregation Principle (ISP), we can break down this large Button component into smaller, more focused components. For example:

JSX code:

const ClickableButton = ({ onClick }) => (
  <button onClick={onClick}>Click Me</button>
);

const HoverableButton = ({ onHover }) => (
  <button onMouseOver={onHover}>Hover Over Me</button>
);
Enter fullscreen mode Exit fullscreen mode

Example 2:

Imagine we have a large Form component that contains multiple fields (name, address, email, password). However, sometimes we only need the email and password fields, not the entire form component.

According to the Interface Segregation Principle (ISP), we can break down the form into smaller parts. For example:

JSX code:

const EmailField = ({ email, onChange }) => (
  <input type="email" value={email} onChange={onChange} />
);

const PasswordField = ({ password, onChange }) => (
  <input type="password" value={password} onChange={onChange} />
);
Enter fullscreen mode Exit fullscreen mode

Now, when we only need the email and password, we can use just those specific components instead of the entire form component. This approach allows us to create a more focused and modular structure, adhering to ISP principles.

Example 3:

Imagine we have a large Dashboard component that includes various user information, graphs, and settings. However, there might be a page where we only need the user settings, yet we are using the entire Dashboard component.

According to the Interface Segregation Principle (ISP), we should break down the large Dashboard component into smaller, more focused parts. For example:

JSX code:

const UserInfo = ({ name, email }) => (
  <div>
    <p>{name}</p>
    <p>{email}</p>
  </div>
);

const UserSettings = ({ settings }) => (
  <div>
    <h3>Settings</h3>
    {/* Code to display the settings */}
  </div>
);
Enter fullscreen mode Exit fullscreen mode

Now, we can utilize these separate parts wherever necessary, allowing us to display only the relevant sections needed for that specific page. This approach ensures that our components are lightweight and tailored to their intended functionality.

Solution:

Following the Interface Segregation Principle (ISP), React components should be designed as separate, small interfaces or props tailored for specific tasks. This approach allows components to be easier to manage and used only as needed, promoting a more efficient and clean codebase.

By breaking down components into smaller, focused parts, we ensure that each component does one thing well, enhancing maintainability and making it easier to adapt or extend functionality in the future. This method also facilitates better reusability, as developers can select only the components that fit their requirements without carrying unnecessary baggage.

Disadvantages of Interface Segregation Principle(ISP)

While the Interface Segregation Principle (ISP) has several advantages, it also comes with some limitations. Below are some disadvantages of ISP:

  1. Need for More Interfaces: Following ISP often requires breaking large interfaces into smaller ones. This can lead to the creation of a large number of interfaces, making code management somewhat complex.

  2. Increased Coding and Maintenance: With many interfaces, each one requires a separate implementation. This increases the workload for developers and can take more time. Additionally, making changes later might necessitate updates in multiple places, complicating maintenance.

  3. Risk of Over-Engineering: ISP can sometimes introduce excessive complexity, especially when too many small interfaces are created. This approach may lead to over-engineering, resulting in unnecessary complexity for the project.

  4. Complex Dependency Management: Using ISP can make components or classes dependent on various interfaces. This can complicate dependency management, as multiple dependencies arise from several interfaces, making it difficult to keep track of them.

Summary

When applying ISP, issues such as the creation of excessive interfaces, increased coding, and management challenges can arise, which may escalate project complexity.

Conclusion

The Interface Segregation Principle (ISP) helps maintain modularity and flexibility in programming. By breaking down large interfaces or components into smaller parts, it eliminates unnecessary complexity. Using ISP allows us to implement only the necessary methods or props in a component, making the code simpler, more reusable, and maintainable. Although it can sometimes lead to an increase in interfaces and code, when applied correctly, it can greatly enhance the organization and effectiveness of software design. Therefore, proper implementation of ISP is essential for improved quality and long-term success in software development.

Top comments (0)