Card 02/ 08
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
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()])Human has lunch
NotImplementedError: robots do not eatimport 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"); }
}Human has lunch
UnsupportedOperationException: robots do not eatinterface 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()]);Human has lunch
Error: robots do not eatIn 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.
← → move through sections and cards