Успадкування повторно використовує код через ієрархію «є різновидом» (Admin extends User). Композиція - через «має» чи «використовує»: об'єкт отримує інші об'єкти й делегує їм роботу.
Проблеми глибокого успадкування:
class Report { public function generate() { /* ... */ } }
class PdfReport extends Report { /* ... */ }
class CachedPdfReport extends PdfReport { /* ... */ }
class CachedEmailedPdfReport extends CachedPdfReport { /* ... */ }
// а потрібен ще CachedEmailedCsvReport...
- комбінаторний вибух: кожна комбінація можливостей - новий клас;
- крихкий базовий клас: зміна в
Reportнепередбачувано ламає нащадків; - жорсткий зв'язок: нащадок залежить від внутрішніх деталей батька (
protected), а не лише від публічного API; - одне успадкування в PHP: клас не може взяти поведінку з двох батьків.
Композицією:
final class ReportGenerator
{
public function __construct(
private ReportFormatter $formatter, // Pdf чи Csv
private ReportDelivery $delivery, // Email чи Storage
private CacheRepository $cache,
) {}
public function run(ReportQuery $query): void
{
$data = $this->cache->remember($query->key(), 3600, fn () => $query->fetch());
$this->delivery->send($this->formatter->format($data));
}
}
Будь-яка комбінація - різні об'єкти в конструкторі, без нових класів. Поведінку можна змінити під час виконання й легко підмінити в тестах.
Інструменти композиції: впровадження залежностей, Strategy, Decorator (обгортання з тим самим інтерфейсом), делегування.
Трейти PHP - механізм повторного використання коду без успадкування, але це радше «копіювання методів» у клас: вони не створюють окремих об'єктів і можуть мати ті самі проблеми прихованих залежностей. Добрі для невеликої поведінки (HasFactory, SoftDeletes), погані як заміна архітектури.
Коли успадкування доречне:
- справжнє відношення «є різновидом», де нащадок повністю замінює батька (принцип Лісков);
- точки розширення фреймворку:
extends Model,extends Controller,extends Command- фреймворк так спроєктований; - абстрактний клас з невеликою спільною логікою і шаблонним методом;
- неглибока ієрархія (1-2 рівні).
Корисне правило: класи за замовчуванням final. Відкривати для успадкування - свідоме рішення, а не випадковість.