Course content
Interface Segregation: Split the OfficeDevice Interface
Medium·Tagssolidinterface-segregationrefactoring
Problem Statement
The starter has a single `OfficeDevice` interface that declares three methods:
```java
interface OfficeDevice {
void print(String document);
void scan();
void fax(String number);
}
```
Three devices implement it:
- `MultiFunctionPrinter` actually supports all three operations.
- `BasicPrinter` only prints, but is forced to provide `scan` and `fax` methods that throw `UnsupportedOperationException`.
- `OldFaxMachine` only faxes, but is forced to provide `print` and `scan` methods that throw.
This violates the Interface Segregation Principle. Two of the three devices are paying for capabilities they do not have. Any caller that holds an `OfficeDevice` reference cannot tell from the type whether `scan` will work or throw, so it has to either guard every call with a try/catch or know the concrete type ahead of time. Both of those are smells.
Refactor the design by splitting `OfficeDevice` into three small interfaces, one per capability. The starter already declares them empty:
```java
interface Printer { / declare void print(String document) / }
interface Scanner { / declare void scan() / }
interface Faxer { / declare void fax(String number) / }
```
After your refactor, the design must expose this exact public API:
- `Printer` declares `void print(String document)`.
- `Scanner` declares `void scan()`.
- `Faxer` declares `void fax(String number)`.
- `MultiFunctionPrinter` implements `Printer`, `Scanner`, and `Faxer`.
- `BasicPrinter` implements `Printer` only and does not expose `scan` or `fax`.
- `OldFaxMachine` implements `Faxer` only and does not expose `print` or `scan`.
The `OfficeDevice` interface and the throwing stub methods on `BasicPrinter` and `OldFaxMachine` should be removed. After the refactor, every method that exists on a device is one the device actually supports.
The validator runs five checks:
1. `MultiFunctionPrinter` supports all three capabilities. The validator casts it to each of `Printer`, `Scanner`, and `Faxer` and confirms every method runs without throwing.
2. `BasicPrinter` implements `Printer` and only `Printer`. Reflection confirms `Printer.class.isAssignableFrom(BasicPrinter.class)` is true while the same check for `Scanner` and `Faxer` returns false. The print method itself works correctly.
3. `OldFaxMachine` implements `Faxer` and only `Faxer`. Same shape as check 2, mirrored.
4. The three small interfaces declare exactly the right methods: `Printer.print(String)`, `Scanner.scan()`, `Faxer.fax(String)`.
5. The unused-method check, which is the strict ISP test. The validator uses reflection to confirm that `BasicPrinter` exposes neither `scan()` nor `fax(String)` through any inheritance path, and that `OldFaxMachine` exposes neither `print(String)` nor `scan()`. This forces the actual structural change. If you keep `BasicPrinter implements OfficeDevice`, the inherited `scan` and `fax` methods are still on its public API, and this check fails.
All five checks fail in the smelly starter. Each requires the actual refactor.
Starter code and validators are available in Java, Python, and C++.
Examples
Example 1
Input
Printer p = new BasicPrinter(); p.print("report.pdf")Output
"(prints \"report.pdf\")"Why
BasicPrinter only implements Printer. Callers that need printing capability ask for a Printer reference.
Example 2
Input
Scanner s = (Scanner) new BasicPrinter()Output
"compile error after refactor (or ClassCastException with current cast syntax)"Why
BasicPrinter does not implement Scanner. Callers that need scanning ask for a Scanner reference, which a BasicPrinter cannot satisfy. The type system tells them so at the cast site.
Example 3
Input
MultiFunctionPrinter mfp = new MultiFunctionPrinter(); mfp.print("doc"); mfp.scan(); mfp.fax("555-1234")Output
"(prints, scans, and faxes)"Why
An MFP truly supports all three capabilities, so it implements all three interfaces.
Constraints
- •Refactor must produce three small interfaces named Printer, Scanner, Faxer with one method each.
- •MultiFunctionPrinter must implement Printer, Scanner, and Faxer.
- •BasicPrinter must implement Printer only.
- •OldFaxMachine must implement Faxer only.
- •BasicPrinter must not expose a scan() or fax(String) method through any inheritance path.
- •OldFaxMachine must not expose a print(String) or scan() method through any inheritance path.
Hints
Stuck? Reveal a nudge toward the right pattern, one step at a time.
Hint 1
Add the missing method to each small interface first. Printer needs `void print(String document)`, Scanner needs `void scan()`, Faxer needs `void fax(String number)`. They are empty in the starter on purpose.
Hint 2
Update the device classes one at a time. MultiFunctionPrinter genuinely supports all three, so it implements Printer, Scanner, and Faxer. The bodies of its print, scan, and fax stay the same.
Hint 3
BasicPrinter only prints. Change its declaration to `class BasicPrinter implements Printer`. Delete the scan and fax methods entirely, including their throw stubs.
Hint 4
OldFaxMachine only faxes. Change its declaration to `class OldFaxMachine implements Faxer`. Delete the print and scan methods entirely.
Hint 5
Once no class implements OfficeDevice, you can delete the OfficeDevice interface. The validator does not require this, but it removes dead code from your design.
Hint 6
The strictest validator check uses Class.getMethod to confirm the unused methods are gone from the inheritance chain. If you leave `implements OfficeDevice` on a device, the inherited methods are still on its public API and the check fails.