A universal remote with 200 buttons for every appliance you'll never own is harder to use than three simple remotes, one per device. Interfaces should offer callers only the methods they actually need, not everything a class could theoretically do.
In Pharmalytics, the ERP I'm building for a pharmaceutical lab's maquila-inventory tracking, I try to keep repository and service interfaces narrow on purpose — favoring a handful of small, focused contracts over one bloated interface that forces every implementing class to support operations it doesn't actually need. Interface Segregation is the discipline behind that instinct: keep each contract honest about what it actually needs.
Trade-off: a class that genuinely needs to both generate a report and export it to PDF now has to implement two interfaces instead of one — narrow contracts mean more of them to wire together when a class legitimately needs several capabilities at once.
FileHandler
SimpleReader
Before
interface ReportGenerator
{
public function generateInventoryReport(): string;
public function generateInvoiceReport(): string;
public function emailReport(string $report): void;
public function exportToPdf(string $report): string;
}
class InventoryReportGenerator implements ReportGenerator
{
public function generateInventoryReport(): string { /* ... */ }
public function generateInvoiceReport(): string { throw new LogicException('n/a'); }
public function emailReport(string $report): void { /* ... */ }
public function exportToPdf(string $report): string { throw new LogicException('n/a'); }
}After
interface GeneratesReport
{
public function generate(): string;
}
interface ExportsToPdf
{
public function toPdf(string $report): string;
}
class InventoryReportGenerator implements GeneratesReport
{
public function generate(): string { /* ... */ }
}