2.2.7 · D4Design Principles

Exercises — SOLID — Interface Segregation Principle

3,580 words16 min readBack to topic

This page assumes only the parent note: the ISP topic note. Every term used below is either defined here or re-earned below.


Vocabulary we will lean on (re-earned here)

Before any exercise, let us nail down three words this page uses constantly, so nothing is "assumed from the parent note."

The picture below is the mental model for the whole page — keep it in mind for every exercise.

Figure — SOLID — Interface Segregation Principle

Level 1 — Recognition

Goal: spot an ISP violation on sight, and name the vocabulary.

Exercise 1.1

Which single sentence is the Interface Segregation Principle?

  • (a) A class should have only one reason to change.
  • (b) No client should be forced to depend on methods it does not use.
  • (c) High-level modules should not depend on low-level modules.
  • (d) Subtypes must be substitutable for their base types.
Recall Solution

(b). Option (a) is the Single Responsibility Principle, (c) is the Dependency Inversion Principle, and (d) is the Liskov Substitution Principle. ISP is specifically about what a client is forced to depend on.

Exercise 1.2

Here is a class. Circle the "smell" that tells you an interface above it is too fat.

class OldPrinter implements Machine {
    public void print(Doc d) { /* real work */ }
    public void scan(Doc d)  { throw new UnsupportedOperationException(); }
    public void fax(Doc d)   { throw new UnsupportedOperationException(); }
}
Recall Solution

The smell is the ==throw new UnsupportedOperationException()== inside methods scan and fax. OldPrinter was forced to implement abilities it does not have. Those two stubbed methods are the fingerprint of a fat interface (Machine) that bundled unrelated roles — two forced dependencies, exactly the orange arrows from the figure above.

Exercise 1.3

Match each word to its meaning.

  1. Client    2. Fat interface    3. Role interface
  • (A) A small interface holding only the cohesive methods one kind of caller needs.
  • (B) Any code that depends on an interface — the caller and the implementer.
  • (C) One interface bundling many unrelated capabilities.
Recall Solution

1→B, 2→C, 3→A. Client is the subtle one: it is not only the code that calls a method. The implementing class is also a client of the interface, because it too is "forced to depend" on every declared method.


Level 2 — Application

Goal: split a concrete fat interface, and count the win.

Exercise 2.1

Given the fat interface below, produce the segregated interfaces and rewrite OldPrinter and AllInOne.

interface Machine {
    void print(Doc d);
    void scan(Doc d);
    void fax(Doc d);
}
// OldPrinter only prints. AllInOne does everything.
Recall Solution

Split by role — one interface per cohesive capability:

interface Printer { void print(Doc d); }
interface Scanner { void scan(Doc d); }
interface Fax     { void fax(Doc d); }
 
class OldPrinter implements Printer {
    public void print(Doc d) { /* real work */ }
}
class AllInOne implements Printer, Scanner, Fax {
    public void print(Doc d) { ... }
    public void scan(Doc d)  { ... }
    public void fax(Doc d)   { ... }
}

Why: OldPrinter's contract is now honest — it advertises only print. A class needing several roles simply implements several small interfaces (composition of roles). This is the right-hand side of the figure above.

Exercise 2.2

Using the parent note's formula for forced (unused) method dependencies, where is the number of methods in the fat interface, the number of implementers, and the count of methods implementer genuinely uses — compute for a Machine interface with methods and five implementers whose genuine usage counts are .

Recall Solution

Here . Plug each implementer in: So the fat interface forces 5 dead method dependencies. After perfect segregation each implementer pulls in exactly its , so — those 5 vanish.

The stacked bar chart below makes this concrete: for each implementer the teal block is the methods it uses and the orange block on top is the forced gap . The total orange height across all five bars is .

Figure — SOLID — Interface Segregation Principle

Exercise 2.3

A canteen scheduler only ever needs to feed workers. After you segregate Worker into Workable and Feedable, what type should the scheduler depend on, and why does this delete a whole class of runtime bugs?

Recall Solution

It should depend only on ==Feedable==. Why the bug disappears: a RobotWorker implements only Workable, so it is not a Feedable. The scheduler cannot even reference a robot — the type system rejects scheduler.feed(robot) at compile time. The old bug (robot.eat() throwing at runtime) is now impossible to write.


Level 3 — Analysis

Goal: reason about which principle is violated, and why.

Exercise 3.1

A teammate says: "Stubbing scan() with throw new UnsupportedOperationException() is fine — the class still implements the interface, so ISP is satisfied." Diagnose exactly which SOLID principle(s) this breaks and explain the mechanism.

Recall Solution

It breaks two:

  • ISP, because the class was forced to depend on a method (scan) it does not use — the stub is the very symptom ISP warns about.
  • LSP, because a caller holding a Machine reference expects scan() to work. Substituting an OldPrinter for a general Machine breaks that expectation at runtime. The subtype does not honour the supertype's contract.

"It compiles" is not the same as "it is substitutable." A compile-time stub is a runtime bomb. The correct fix is to remove scan from the interface OldPrinter implements — i.e. segregate.

Exercise 3.2

Is the following a violation of SRP, ISP, or both? Justify by pointing at "reasons to change" vs "forced dependency."

// One class, one responsibility (managing a user account),
// but it exposes a giant interface to very different callers.
interface UserAccount {
    // used by the login screen
    boolean checkPassword(String p);
    // used by the admin dashboard
    void banUser();
    // used by the billing system
    Invoice generateInvoice();
}
Recall Solution

This is primarily an ISP problem, and not necessarily an SRP problem.

  • SRP asks: does the class have more than one reason to change? Possibly not — "managing an account" can be argued as one responsibility.
  • ISP asks: are clients forced to depend on methods they don't use? Yes. The login screen only needs checkPassword, yet it depends on a type that also declares banUser and generateInvoice. Change generateInvoice's signature and the login screen recompiles for no reason.

Fix: split into role interfaces Authenticatable, Moderatable, Billable, each handed to the client that needs it. This shows ISP is "SRP for interfaces"—about callers, not about the class's internal reasons to change.

Exercise 3.3

Recompute the forced-dependency count as a saving. A fat interface has methods and implementers with usage . How many forced dependencies does segregation eliminate, and what fraction of all declared obligations were dead weight? (Total declared obligations .)

Recall Solution

Forced dependencies: Total declared obligations . Fraction that was dead weight: Half of every "must implement" here was a lie removed by ISP.

Why every term is (the fact): counts methods the implementer uses out of the interface. You cannot use a method that is not there — the interface only offers of them — so at most , i.e. for every . Hence each gap is never negative: forced dependencies can only add, never subtract.


Level 4 — Synthesis

Goal: combine ISP with other principles and patterns.

Exercise 4.1

You have a legacy LegacyReporter class with a huge public API (12 methods). New code should depend only on a 2-method Summariser role you define. You cannot modify LegacyReporter. Which design pattern from the connections list lets you present the small role interface to new clients, and sketch how.

Recall Solution

Use the Adapter pattern. Define the segregated role interface, then wrap the fat legacy class:

interface Summariser {
    String headline();
    int total();
}
 
class ReporterAdapter implements Summariser {
    private final LegacyReporter legacy;
    ReporterAdapter(LegacyReporter legacy) { this.legacy = legacy; }
    public String headline() { return legacy.buildFullReport().getTitle(); }
    public int total()       { return legacy.computeGrandTotal(); }
}

Why this synthesises ISP + Adapter: new clients depend only on the thin Summariser (ISP satisfied), while the adapter absorbs the fat legacy API behind the scenes. The adapter is precisely "a translator between a segregated interface and an un-segregatable class."

Exercise 4.2

Explain how ISP feeds Dependency Inversion. Specifically: why is a small role interface a better abstraction to "depend on the abstraction, not the concretion" than a fat one?

Recall Solution

DIP says high-level modules should depend on abstractions. The quality of that abstraction matters:

  • A fat abstraction re-imports all the coupling DIP was trying to break — you inverted the dependency arrow but still depend on 12 methods, so any of the 12 changing ripples up.
  • A small role interface exposes only what the high-level module needs, so the inverted dependency is minimal and stable.

In short: ISP makes the abstractions DIP depends on thin and stable. ISP + DIP together give you both "point at an interface" and "point at a tiny one."

Exercise 4.3

Show, with the formula, that segregation into role interfaces can never increase forced dependencies. That is, argue always, and state when equality holds.

Recall Solution

Under perfect segregation each implementer implements exactly the interfaces whose methods it uses, so it is forced to supply exactly methods and no more. Its forced (unused) count is . Summing: Since every term of has (you cannot use more methods than exist), each term is , so . Equality () holds only when every implementer already uses all methods ( for all ) — i.e. the interface was never fat to begin with, so there is nothing to segregate.


Level 5 — Mastery

Goal: optimise, generalise, and defend edge cases.

Exercise 5.1

Generalise the win metric. A team keeps a fat interface but adds one new method to it. There are implementers, and only of them genuinely need the new method (). Let denote the change in forced dependencies caused by this single addition (i.e. new value of minus the old value). Find and say what it reveals about the cost of change for fat interfaces.

Recall Solution

Define = (forced dependencies after the addition) − (before). The new method raises by 1, and every implementer must now supply it — but only of them use it. The other gain one forced dependency each, while the that use it gain zero. Therefore Interpretation: one line added to a fat interface imposes new dead implementations — the cost of a change grows with the number of unrelated implementers. With segregation the new method lives in its own role interface that only the needers implement, so . This is the mechanical reason "a fat interface has many reasons to change / many things to break."

Exercise 5.2

Design edge case: an interface Stream has read() and write(). You discover read-only files exist. A colleague proposes splitting into Readable and Writable. Another says "no — keep Stream, they're cohesive." Give the decision rule and resolve it.

Recall Solution

Decision rule: split when there exists at least one real client that uses one method but is forced into the other. Cohesion is measured by how clients use the methods together, not by how related they sound.

  • Read-only files are a concrete client that needs read() but must not be forced to implement write(). That existence proof means the methods are not cohesive for all clients → split into Readable and Writable.
  • A read-write file simply implements both (class RWFile implements Readable, Writable).

So the second colleague is wrong because a divergent client exists. Had every stream always been read-write, keeping Stream would be correct (over-splitting is the L2 trap).

Exercise 5.3 (Degenerate & vacuous cases)

Evaluate in each edge case and say what it means. Pay special attention to the vacuous cases where a sum has no terms.

  • (a) (interface has zero implementers — the vacuous case).
  • (b) (interface declares zero methods — a marker interface).
  • (c) Every .
  • (d) Every .
Recall Solution
  • (a) (vacuous): the sum has no terms, so by the convention that an empty sum equals , . An interface nobody implements forces nothing — it is harmless (though possibly dead code). This vacuous truth matters: "there are no implementers to force" is why the metric is zero, not because usage was perfect.
  • (b) : each term is , but forces , so every term is . A marker interface with no methods can force nothing.
  • (c) all : every term . The interface is perfectly cohesive; ISP has nothing to fix.
  • (d) all : every term , the maximum. Every implementer is dead weight in every method — the worst possible fat interface, where nobody uses anything they're forced to implement.
Figure — SOLID — Interface Segregation Principle

How ISP sits among the other SOLID principles

Mastery means knowing not just ISP but where it touches its neighbours. Read this map like a set of quadrants — each edge is "ISP hands off to / is confused with" another principle.

is SRP for interfaces

stub that throws breaks

thin roles are ideal abstractions

raises cohesion lowers coupling

class level vs interface level

ISP - clients depend only on what they use

SRP - one reason to change

LSP - subtypes honour the contract

DIP - depend on abstractions

Cohesion and Coupling

Recall How to read the map
  • ISP → SRP: same "keep it small" spirit, but SRP judges a class's reasons to change, ISP judges what a caller is forced to depend on (Exercise 3.2).
  • ISP → LSP: a forced stub that throws is exactly the LSP-breaking substitution (Exercise 3.1).
  • ISP → DIP: thin role interfaces are the best abstractions to invert onto (Exercise 4.2).
  • ISP → Cohesion: segregation groups methods by cohesive role, raising cohesion and cutting accidental coupling (L2 trap).

Score yourself

Recall What mastery looks like
  • L1–L2: you can name ISP and mechanically split a fat interface.
  • L3: you can tell ISP from SRP and spot the LSP break in a stub.
  • L4: you combine ISP with Adapter and DIP.
  • L5: you can prove the win formula's bounds and defend every degenerate and vacuous case.

Connections