Interface Segregation & Dependency Inversion in Java
Small interfaces; depend on abstractions.
A fat interface
Interface Segregation: clients shouldn't depend on methods they don't use. A big Machine interface forces every implementer to provide all three methods. The cheap printer can only lie: throw UnsupportedOperationException or do nothing.
interface Machine {
void print(Doc d);
void scan(Doc d);
void fax(Doc d);
}
class CheapPrinter implements Machine {
public void fax(Doc d) { throw new
UnsupportedOperationException(); }
// ...
}It compiles. So what's wrong?
CheapPrinter.fax() compiles fine. Why is an empty or throwing implementation a design problem?
Think about it, then reveal the answer
Because callers can't trust the type. Machine promises fax() works, but it fails at runtime or silently does nothing. A method that's only implemented by throwing is a strong sign the interface is too fat and bundles unrelated roles.
Split by role
interface Machine {
void print(Doc d);
void scan(Doc d);
void fax(Doc d);
}Every device must fake what it can't do. Adding a canFax() flag or throwing defaults doesn't fix it.
interface Printer { void print(Doc d); }
interface Scanner { void scan(Doc d); }
interface Fax { void fax(Doc d); }
class Office
implements Printer, Scanner, Fax {}The cheap printer implements only Printer; a multifunction device implements all three.
The hard-wired dependency
Dependency Inversion: high-level policy shouldn't depend on low-level details. The MySqlDb field welds a concrete database class into business code: you can't swap the database or test without it. Depend on an abstraction like ReportStore and receive it through the constructor.
class ReportService {
private final ReportStore store;
private final MySqlDb db = new MySqlDb();
ReportService(ReportStore store) {
this.store = store;
}
}Who owns the interface?
The twist that gives DIP its name: the business module owns the interface, shaped by what it needs. The database code then implements it, so the source dependency points from the detail toward the policy. Not the driver library, not a shared 'utils' module.
// module: checkout (business)
interface OrderStore { void save(Order o); }
// module: mysql (detail)
class MySqlOrderStore
implements OrderStore { ... }Your SOLID map
You've now met S, O, I and D. Which letter says a subtype must work wherever its base type is expected?
SLID
Show the answer
L, the Liskov Substitution Principle. The full set: S one reason to change, O extend without editing, L subtypes are substitutable, I small client-specific interfaces, D depend on abstractions.
Swappable details
DIP is why teams can switch MySQL to Postgres, or a real payment API to a fake in tests, by changing one class. Architectures like "ports and adapters" are built on it. In reviews, a new of a database or HTTP client inside business logic is a classic flag.
Key takeaways
- Fat interfaces lead to empty or throwing implementations
- Split interfaces by client role: Printer, Scanner, not Machine
- Business code depends on interfaces, not concrete drivers
- Concrete details are plugged in from outside
💡 A wall socket is the abstraction: your lamp doesn't care which power plant is behind it.
Robert C. Martin described Interface Segregation after consulting for Xerox, where one huge Job class served every printer task, so even tiny changes forced rebuilding almost everything.
Practice questions
A cheap printer must implement Machine. What does Interface Segregation suggest?
interface Machine {
void print(Doc d);
void scan(Doc d);
void fax(Doc d);
}- Implement scan and fax as empty methods
- Make Machine an abstract class whose defaults throw
- Add a boolean canFax() method
- Split it into Printer, Scanner and Fax interfaces
Check your answer
Split it into Printer, Scanner and Fax interfaces. Small role interfaces let the cheap printer implement only Printer, while a multifunction device implements all three.
Under Dependency Inversion, which module should own the OrderStore interface?
- The database driver library
- The high-level business module that uses it
- Whichever module compiles first
- A shared 'utils' module that everything depends on
Check your answer
The high-level business module that uses it. The abstraction is shaped by what the policy needs. The database adapter then depends on the business module's interface, which inverts the usual source-code dependency.