CMD Guide
HomeOO & Low-Level DesignSOLID Principles

Techniques to Identify ISP Violations

Every ISP smell reduces to one mechanical fact: an interface is a compile-time contract that a class must satisfy in full before the compiler will let it exist — so the moment an interface bundles a method some implementer cannot honestly fulfill, the language forces that implementer to either lie (a stub that throws) or pollute its public surface with a method it never wanted, and every caller that depends on the interface inherits the false promise. The techniques below are not four unrelated checklist items; they are four observable shadows of that single failure. The skill is learning to read each shadow back to the contract that cast it.

The one mechanism, four shadows

An interface I with methods {m1…mn} says: any reference of type I may receive any of m1…mn, and the type system guarantees a sensible answer. A class C implements I is the compiler-enforced promise that C backs all of them. ISP is violated exactly when that promise is partially false for some C. You cannot see "partial falseness" directly, so you hunt for the artifacts it leaves behind:

Smell (the shadow)What you literally seeWhy it mechanically proves an over-wide contract
Empty / throwing overridethrow new UnsupportedOperationException() or an empty {} bodyThe class was forced to declare a method to satisfy the type, then had nothing true to put in it. The stub is the false part of the promise made visible.
Fat interfaceOne interface with many methods, no single implementer using all of themIf no class needs the whole set, the set was never one contract — it is several contracts welded together, and welding forces every implementer to adopt the union.
Capability check before callif (x instanceof Recorder) / if (x.supportsRecord()) at the call siteCallers downcast or query because the static type over-promises: they can't trust the contract, so they re-derive the real capability at runtime — the type system has stopped doing its job.
Shotgun change on addAdding one method to the interface breaks N unrelated classes that must add a stubA change isolated to one capability rippling into unrelated implementers means those implementers were coupled to a capability they don't use — the definition of an over-wide contract.

One thing that is deliberately not on this list: "low cohesion." A low-cohesion interface is not a fifth, independent test — it is the fat-interface smell described from the producer's side, so it collapses into row two. The mechanically distinct, callable tests are the four above, and each one traces straight back to "the contract promised more than an implementer can keep."

Worked trace: from a green compile to a production NPE

Take the classic over-wide contract. A real implementer, a real caller, and we follow concrete values through the type system.

java
// The over-wide contract: it bundles three independent capabilities.
interface MultiFunctionDevice {
    String print(String doc);   // printers + MFPs
    byte[] scan();              // scanners + MFPs
    void fax(String number);    // fax machines + MFPs
}

// A cheap office printer. It can print. It cannot scan or fax.
class BasicPrinter implements MultiFunctionDevice {
    public String print(String doc) {
        return "printed: " + doc;
    }
    public byte[] scan() {                       // forced stub #1
        throw new UnsupportedOperationException("BasicPrinter cannot scan");
    }
    public void fax(String number) {             // forced stub #2
        throw new UnsupportedOperationException("BasicPrinter cannot fax");
    }
}

// A caller that only needs to scan-then-archive. It does not care about print/fax.
class ArchiveJob {
    void archive(MultiFunctionDevice dev) {      // over-wide parameter type
        byte[] image = dev.scan();               // line A
        store(image);
    }
    void store(byte[] b) { /* ... */ }
}

Now trace one concrete run: new ArchiveJob().archive(new BasicPrinter()).

StepWhat the type system / runtime doesResult
1. Compile BasicPrinterChecks all 3 contract methods are present. They are (two are stubs).Compiles green. The lie passes the type check — stubs satisfy the signature, not the meaning.
2. Compile archive(...)Param type is MultiFunctionDevice; BasicPrinter is-a one.Compiles green. The compiler has no way to know this particular device can't scan.
3. Run, hit line ADynamic dispatch resolves scan() to BasicPrinter.scan.Executes the stub.
4. Stub body runsthrow new UnsupportedOperationExceptionRuntime crash in production, not a compile error in the IDE. The cost of the over-wide contract was deferred to the worst possible moment.

Why the naive version is wrong: the stubbed scan()/fax() are not "defensive" — they convert a type error (this object should never have been accepted where scanning is required) into a runtime error. The compiler would have caught it for free if archive had asked for the narrow type Scanner instead. Stubbing throws away the one guarantee the type system was offering.

The fix and what it buys you

Split the welded contract into the capabilities that actually vary independently, then let each caller demand exactly the slice it uses.

java
interface Printer { String print(String doc); }
interface Scanner { byte[] scan(); }
interface Fax     { void fax(String number); }

// BasicPrinter now implements ONLY what is true of it. No stubs exist.
class BasicPrinter implements Printer {
    public String print(String doc) { return "printed: " + doc; }
}

// An MFP composes the capabilities it genuinely has.
class OfficeMfp implements Printer, Scanner, Fax {
    public String print(String doc) { return "printed: " + doc; }
    public byte[] scan() { return new byte[]{ 0x1, 0x2 }; }
    public void fax(String number) { /* dial + send */ }
}

class ArchiveJob {
    void archive(Scanner dev) {        // narrow type: only scanners get in
        store(dev.scan());
    }
    void store(byte[] b) { /* ... */ }
}

Re-run the trace: archive(new BasicPrinter()) now fails at compile timeBasicPrinter is not a Scanner. The bug moved from a 2 a.m. production exception to a red squiggle in the editor. That is the entire payoff of ISP made concrete: violations become uncompilable instead of merely untested. (Go reaches the same end with implicit, tiny interfaces — io.Reader, io.Writer — where a function asking for io.Reader simply will not accept a type lacking Read; the segregation is the idiomatic default rather than a refactor.)

diagram
diagram

Pitfalls

When to segregate — and when not to

Decision signal: segregate when implementers of a contract form distinct subsets over its methods — some classes use {print}, others {scan, fax} — and callers likewise depend on different slices. If you can draw two implementers whose used-method sets don't overlap, the interface should split along that boundary.

The trade-off you are buying:

Segregated interfaces (ISP)Alternative: one fat interface + stubs
Wrong-capability bug caughtCompile timeRuntime (deferred, costly)
Number of typesMore — N capability interfacesFewer — one type
Adding a capabilityNew interface; only real implementers touchedShotgun edit: every implementer adds a stub
Reading the codeA param type is the requirementMust read the body to know what's really used
CostIndirection; composing multiple interfaces on rich classes (MFP implements 3)Cheap to write, expensive to maintain & trust

Versus the Adapter alternative: when the fat interface is third-party and you cannot split it, wrap it in a narrow Scanner adapter you own; your code depends on the slim type while the adapter absorbs the fat dependency. You gain the compile-time guarantee at the cost of one wrapper class and a layer of indirection.

One-liner: choose segregation when implementers and callers use different subsets of the methods and you want wrong usage to be uncompilable; prefer a single interface when every implementer genuinely backs every method (it is truly cohesive) and splitting would only multiply types without removing a single stub.

Takeaways


Sources: Robert C. Martin, Agile Software Development: Principles, Patterns, and Practices (the original ISP and the Xerox printer case) and Clean Architecture; the Java Language Specification on interface implementation and dynamic dispatch; and the Go standard library's io.Reader/io.Writer as the canonical small-interface idiom.

🤖 Don't fully get this? Learn it with Claude

Stuck on Techniques to Identify ISP Violations? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🎨 Explain it visually

Build the mental picture, not memorization.

I just read a lesson on **Techniques to Identify ISP Violations** (OO & Low-Level Design) and want to truly understand it. Explain Techniques to Identify ISP Violations from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🤔 Walk me through it (interactive)

Socratic — adapts to where you're stuck.

Teach me **Techniques to Identify ISP Violations** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧪 Quiz me & fix my gaps

Active recall exposes what you missed.

Quiz me on **Techniques to Identify ISP Violations** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🧠 Make it stick

Intuition + hook + flashcards for long-term memory.

Help me remember **Techniques to Identify ISP Violations** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes