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.
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.