Card 02/ 08

CodeDifficulty: Beginner3 min

Spotting an Interface Segregation Violation

A Robot that implements eat() by raising an error.

An interface segregation violation shows up as a class that implements methods it cannot honour, usually by raising an error or doing nothing. This card shows a Worker interface with work, eat and sleep in Python, Java and TypeScript, a Robot forced to implement all three, and what happens when a lunch break reaches it.

A Robot forced to implement eat and sleep

py
from abc import ABC, abstractmethod


class Worker(ABC):
    @abstractmethod
    def work(self): ...

    @abstractmethod
    def eat(self): ...

    @abstractmethod
    def sleep(self): ...


class Human(Worker):
    def work(self):
        return "Human assembles a chair"

    def eat(self):
        return "Human has lunch"

    def sleep(self):
        return "Human sleeps"


class Robot(Worker):
    def work(self):
        return "Robot assembles a chair"

    def eat(self):
        raise NotImplementedError("robots do not eat")

    def sleep(self):
        raise NotImplementedError("robots do not sleep")


def lunch_break(workers):
    for worker in workers:
        try:
            print(worker.eat())
        except NotImplementedError as error:
            print(f"NotImplementedError: {error}")


lunch_break([Human(), Robot()])
OutputRan with python:3.12-slim (Python 3.12.13)
Human has lunch
NotImplementedError: robots do not eat
java
import java.util.List;

public class IspWorker {
    static void lunchBreak(List<Worker> workers) {
        for (Worker worker : workers) {
            try {
                System.out.println(worker.eat());
            } catch (UnsupportedOperationException error) {
                System.out.println("UnsupportedOperationException: " + error.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        lunchBreak(List.of(new Human(), new Robot()));
    }
}

interface Worker {
    String work();

    String eat();

    String sleep();
}

class Human implements Worker {
    public String work() { return "Human assembles a chair"; }

    public String eat() { return "Human has lunch"; }

    public String sleep() { return "Human sleeps"; }
}

class Robot implements Worker {
    public String work() { return "Robot assembles a chair"; }

    public String eat() { throw new UnsupportedOperationException("robots do not eat"); }

    public String sleep() { throw new UnsupportedOperationException("robots do not sleep"); }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
Human has lunch
UnsupportedOperationException: robots do not eat
ts
interface Worker {
  work(): string;
  eat(): string;
  sleep(): string;
}

class Human implements Worker {
  work(): string { return "Human assembles a chair"; }
  eat(): string { return "Human has lunch"; }
  sleep(): string { return "Human sleeps"; }
}

class Robot implements Worker {
  work(): string { return "Robot assembles a chair"; }
  eat(): string { throw new Error("robots do not eat"); }
  sleep(): string { throw new Error("robots do not sleep"); }
}

function lunchBreak(workers: Worker[]): void {
  for (const worker of workers) {
    try {
      console.log(worker.eat());
    } catch (error) {
      console.log(`Error: ${(error as Error).message}`);
    }
  }
}

lunchBreak([new Human(), new Robot()]);
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
Human has lunch
Error: robots do not eat

In each language the human has lunch and the robot throws. The compiler and the abstract base class were both satisfied, because the robot does provide eat. What it provides is a refusal.

Read the two signs together. The stubs in Robot are the implementer's side of the problem: two methods written only to satisfy the interface. The try in lunch_break is the client's side: code that was handed a Worker and had to defend itself against one. Either sign alone is worth a second look. Both together mean the interface asks for more than its implementers share.

The fix is not a better stub. Returning an empty string from eat would hide the failure instead of removing it. The interface needs to be split by the clients that call it.