Search code examples
javasolid-principlesinterface-segregation-principle

How to achieve polymorphism with the Interface Segregation Principle?


My goal is to understand the Interface Segregation Principle and achieve polymorphism at the same time.

My expected result: I can achieve polymorphism with the Interface Segregation Principle.

My actual result: No I can't. I am forced to create boilerplate and use the Liskov Substitution Principle (If there is a Worker, there must be a Worker that can't eat, so create an interface for Worker that can eat that extends Worker). I think I misunderstand the Interface Segregation Principle.

This is the code that violates the Interface Segregation Principle.

public interface IWorker {
  void work();
  void eat();
}

class Human implements IWorker {
  public void work() {
    System.out.println("Human is working.");
  }

  public void eat() {
    System.out.println("Human is eating.");
  }
}

class Robot implements IWorker {
  public void work() {
    System.out.println("Robot is working.");
  }

  public void eat() {
    throw new UnsupportedOperationException("Robot cannot eat");
  }
}

I was told to separate the interface into 2.

public interface IEatable {
  void eat();
}

interface IWorkable {
  void work();
}

class Human implements IWorkable, IEatable {
  public void work() {
    System.out.println("Human is working.");
  }

  public void eat() {
    System.out.println("Human is eating.");
  }
}

class Robot implements IWorkable {
  public void work() {
    System.out.println("Robot is working.");
  }
}

The solution is to use the Liskov Substitution Principle.

public interface IWorkable {
  void work();
}

interface IEatable {
  void eat();
}

interface IWorker extends IWorkable {
}

interface IHumanWorker extends IWorker, IEatable {
}

Solution

  • Your second step looks good, you have split the interface in two more specific interfaces. It does not make sense for a Robot to "eat". (I dont really get the third step)

    On the caller side you can now work with your abstractions:

    //Polymorphism
    List<IWorkable> workers = Arrays.asList(new Robot(), new Human());
    //do some work
    
    List<IEatable> eaters = Arrays.asList(new Human(), new Human());
    //send for lunch break
    

    If you want to have both behaviours in the same thing, than your abstraction/design seems to be wrong, since Robots cannot eat by definition (indicated by the code smell of not implemented methods).

    A Robot is not an IWorker (your first code), because it does not full fill the (full) contract (interface, eat method), no matter how similar it seems to be.