Card 03/ 08

WalkthroughDifficulty: Intermediate4 min

Refactoring to Interface Segregation, Step by Step

Split Worker by what each caller uses, then combine pieces where a caller needs several.

An interface is split by looking at its clients, not at its methods. This card takes the Worker interface from the violation and splits it into Workable, Feedable and Restable, with a combined ShiftWorker for the one caller that needs two of them. The robot then implements only work, and nothing raises.

Splitting Worker by its clients

List each client and what it calls

The assembly line calls work. The lunch break calls eat. The shift planner calls work and then eat. Nothing in this program calls sleep yet, but the human can sleep.

Give each group of calls its own interface

Create Workable with work, Feedable with eat and Restable with sleep. Each one is the exact view one client needs.

Combine where a client needs several

The shift planner needs both work and eat, so declare ShiftWorker as an interface that extends Workable and Feedable. Combining small interfaces is cheap; splitting a wide one later is not.

Re-declare the implementers and narrow the callers

Human implements ShiftWorker and Restable. Robot implements Workable only, and its stubs are deleted. Each function now takes the narrowest interface it uses.

In the final code below, each function names the smallest interface it needs. In Python the interfaces are Protocol classes, so Human and Robot match them by shape without inheriting from them.

Worker split into Workable, Feedable and Restable

py
from typing import Protocol


class Workable(Protocol):
    def work(self) -> str: ...


class Feedable(Protocol):
    def eat(self) -> str: ...


class Restable(Protocol):
    def sleep(self) -> str: ...


class ShiftWorker(Workable, Feedable, Protocol):
    pass


class Human:
    def work(self) -> str:
        return "Human assembles a chair"

    def eat(self) -> str:
        return "Human has lunch"

    def sleep(self) -> str:
        return "Human sleeps"


class Robot:
    def work(self) -> str:
        return "Robot assembles a chair"


def run_line(workers: list[Workable]) -> None:
    for worker in workers:
        print(worker.work())


def lunch_break(workers: list[Feedable]) -> None:
    for worker in workers:
        print(worker.eat())


def plan_shift(worker: ShiftWorker) -> None:
    print(f"{worker.work()}, then {worker.eat().lower()}")


run_line([Human(), Robot()])
lunch_break([Human()])
plan_shift(Human())
OutputRan with python:3.12-slim (Python 3.12.13)
Human assembles a chair
Robot assembles a chair
Human has lunch
Human assembles a chair, then human has lunch
java
import java.util.List;

public class IspWorkerSplit {
    static void runLine(List<? extends Workable> workers) {
        for (Workable worker : workers) System.out.println(worker.work());
    }

    static void lunchBreak(List<? extends Feedable> workers) {
        for (Feedable worker : workers) System.out.println(worker.eat());
    }

    static void planShift(ShiftWorker worker) {
        System.out.println(worker.work() + ", then " + worker.eat().toLowerCase());
    }

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

interface Workable {
    String work();
}

interface Feedable {
    String eat();
}

interface Restable {
    String sleep();
}

interface ShiftWorker extends Workable, Feedable {}

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

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

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

class Robot implements Workable {
    public String work() { return "Robot assembles a chair"; }
}
OutputRan with eclipse-temurin:21-jdk (Java 21.0.12)
Human assembles a chair
Robot assembles a chair
Human has lunch
Human assembles a chair, then human has lunch
ts
interface Workable {
  work(): string;
}

interface Feedable {
  eat(): string;
}

interface Restable {
  sleep(): string;
}

interface ShiftWorker extends Workable, Feedable {}

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

class Robot implements Workable {
  work(): string { return "Robot assembles a chair"; }
}

function runLine(workers: Workable[]): void {
  for (const worker of workers) console.log(worker.work());
}

function lunchBreak(workers: Feedable[]): void {
  for (const worker of workers) console.log(worker.eat());
}

function planShift(worker: ShiftWorker): void {
  console.log(`${worker.work()}, then ${worker.eat().toLowerCase()}`);
}

runLine([new Human(), new Robot()]);
lunchBreak([new Human()]);
planShift(new Human());
OutputRan with node:22-slim (Node 22.23.1), TypeScript 5.9.3
Human assembles a chair
Robot assembles a chair
Human has lunch
Human assembles a chair, then human has lunch

The robot works on the line beside the human, and only the human goes to lunch. Passing a robot to lunch_break is now a compile error in Java and TypeScript, and a type checker error in Python, rather than an exception at run time.

Notice what the split did not require: no method changed and no behaviour moved. Interface segregation is often the cheapest SOLID refactoring, because it changes declarations, not logic. The work is in finding the clients, which is why the first step is a list of callers rather than a list of methods.