The fix is to split Machine into small interfaces, one for each capability:
Printerhandles printing.Scannerhandles scanning.Copierhandles copying.
A caller now asks for the smallest role it needs. For example, a method that prints invoices can take a Printer. It can call print and nothing else, because scanning and copying are not visible to it.
How This Design Follows ISP
SimplerPrinterimplements onlyPrinterbecause printing is its only real capability.- It no longer contains fake
scan()orcopy()methods. MultiPurposeMachineimplementsPrinter,Scanner, andCopierbecause it genuinely supports all three.- Print-only callers depend on
Printer. Scan callers depend onScanner. - If code asks for a
Scannerand you pass aSimplerPrinter, Java refuses to compile it. The failure moves from runtime to compile time.
One class can implement several small interfaces. ISP does not mean one class per operation. It controls what each caller can see and depend on.
Benefits of This Approach
- Honest APIs: the interfaces a class implements match what it can really do.
- Safer calls: no
UnsupportedOperationExceptionfor an operation the type seemed to promise. - Smaller dependencies: changing scanner behavior does not affect a caller that only prints.
- Simpler tests: a printing test needs a tiny
Printerfake, not a fullMachinefake. - Easy to extend: a printer and scanner device can implement just those two interfaces, without pretending it can copy.
- Less access: callers cannot reach unrelated operations by accident.
There is a cost: more interface names and files. Do not split into one-method interfaces by habit. Split when different callers use different slices, or when some implementations cannot support the whole interface.
A naming detail: Java already has a class called java.util.Scanner. In larger code, DocumentScanner is clearer and avoids a mix-up.
Compare with BadCode and read ISP-Notes.md for the restaurant and notification examples.