2

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 {
}
Jason Rich Darmawan
  • 1,607
  • 3
  • 14
  • 31
  • I don't quite understand what your problem is. Using IWorkable as let's say a function argument already let's you use polymorphism – Raildex May 24 '21 at 17:31

2 Answers2

1

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.

HectorLector
  • 1,851
  • 1
  • 23
  • 33
  • Ah. I see, you use interface on the lowest level as possible (no need to create IWorker or IHumanWorker). This is what I am looking for, the first code is not a problem then. – Jason Rich Darmawan May 25 '21 at 07:29
0

I'd recommend using abstract classes for that matter instead of interfaces. If you need every Workable to work, then you make the method abstract. If it's only optional that they eat, you don't. An example of that would be:

abstract class Workable {

    protected String name;

    public Workable(String name) {
        this.name = name;
    }

    protected abstract void work();

    public void eat() {
        System.err.println("\"" + name + "\" can't eat");
    }
}

class Human extends Workable {
    public Human(String name) {
        super(name);
    }
    @Override
    public void work() {
        System.out.println("Human " + name + " is working!");
    }

    @Override
    public void eat() {
        System.out.println("Human " + name + " is eating!");
    }
}

class Robot extends Workable {
    public Robot(String name) {
        super(name);
    }

    public void work() {
        System.out.println("Robot " + name + " is working!");
    }
}


public class Test {
    public static void main(String[] args) {
        Workable[] workers = new Workable[] {
                new Human("Jerry"),
                new Robot("XAE12")
        };
        for (Workable worker : workers) {
            worker.work();
            worker.eat();
        }
    }
}

I'm not sure if I understood your question correctly tho, so please let me know if that helped you.

Cactus Coder
  • 63
  • 1
  • 5
  • I think this violates the Liskov Substitusion Principle in a reverse way? The robot should not have eat method. Or is the LSP actually recommends this? I like the idea to assume method error in the abstract class, so you are forced to make override the method on each new client. – Jason Rich Darmawan May 24 '21 at 18:40
  • That's why the robot doesn't have the eat method. I just realised that I forgot to post the output, I'll edit it once I'm at my pc again. You can also throw an exception in the original method if you like, the important thing is that you don't override the eat method in the robot class. – Cactus Coder May 24 '21 at 19:27