Успадкування - найсильніший зв'язок між класами: нащадок залежить від усіх деталей предка, зокрема захищених. Будь-яка зміна в базовому класі може тихо зламати десяток нащадків - це називають проблемою крихкого базового класу.
Типові симптоми:
- Ієрархія росте вглиб:
Report→PdfReport→ColoredPdfReport→ ... Нова комбінація ознак - новий клас. - Нащадок перевизначає метод, щоб «вимкнути» поведінку предка, яка йому не потрібна.
- Базовий клас обростає
protected-хелперами, бо так зручніше нащадкам.
Композиція - об'єкт отримує інші об'єкти і делегує їм роботу:
final class ReportExporter
{
public function __construct(
private Formatter $formatter, // PDF, CSV, HTML
private Storage $storage, // диск, S3
) {}
public function export(Report $report): string
{
return $this->storage->put($this->formatter->format($report));
}
}
Тепер формат і сховище комбінуються вільно, кожне тестується окремо, і змінити одне можна, не чіпаючи іншого.
Успадкування доречне, коли є справжнє «є різновидом» і спільний контракт стабільний: власні винятки від RuntimeException, моделі від Model. Корисна звичка - робити класи final за замовчуванням: тоді успадкування стає свідомим рішенням, а не випадковістю.