Склад над спадщиною, але


13

Я намагаюся навчити себе інженерії програмного забезпечення і зустрічаюсь із суперечливою інформацією, яка мене бентежить.

Я вивчав OOP, що таке абстрактні класи / інтерфейси та як ними користуватися, але потім я читаю, що слід «надавати перевагу композиції над успадкуванням». Я розумію склад - це коли один клас створює / створює об'єкт іншого класу для використання / взаємодії з цим функціоналом нового об'єкта.

Отже, моє запитання ... Чи повинен я використовувати абстрактні класи та інтерфейси? Щоб не створювати абстрактний клас і не розширювати / успадковувати функціональність цього абстрактного класу в конкретних класах, а натомість просто складати нові об’єкти, щоб використовувати функціональність іншого класу?

Або я повинен використовувати склад І успадковувати з абстрактних класів; використовуючи їх обох спільно один з одним? Якщо так, чи могли б ви навести кілька прикладів того, як це діятиме та його переваги?

Оскільки мені найбільше подобається PHP, я використовую його для вдосконалення своїх навичок OOP / SE перед тим, як перейти на інші мови та передати мої нещодавно придбані навички SE, тому приклади, що використовують PHP, будуть дуже вдячні.


2
"Композиція прихильності над успадкуванням" - дурна ідея, яка має стільки ж сенсу, скільки "прихильність пилок над свердлами". Це два різних інструменти, які доцільно використовувати у двох різних випадках використання, а часи, коли ви можете реально використовувати будь-який взаємозамінний, насправді є досить рідкісними.
Мейсон Уілер

2
"Фаворит X над Y" не означає ніколи не використовувати Y, просто подумайте, чи буде X краще
Річард Тінгл

2
"Композиція переваг над успадкуванням" більш точно виражається як "Ніколи, ніколи, ніколи не використовуй спадкування класів", з уточненням, що реалізація інтерфейсу не є спадщиною, і якщо обрана мова підтримує лише абстрактні класи, а не інтерфейси, успадковуючи на 100% абстрактний клас також можна розглядати як "не спадщину".
Девід Арно

1
@MasonWheeler, не існує дійсних випадків використання для спадкування. Це принаймні настільки "зла" особливість, як "гото".
Девід Арно

1
@DavidArno Це просто смішно. Спадкування - одна з найкорисніших, продуктивних особливостей, коли-небудь розвинута за всю історію програмування. Як і з будь-яким корисним, існує безліч способів зловживати ним, але при правильному використанні це значно збільшує потужність і продуктивність вашої роботи.
Мейсон Уілер

Відповіді:


19

Композиція над успадкуванням означає, що коли ви хочете повторно використовувати або розширити функціональність існуючого класу, часто доцільніше створити інший клас, який буде "обгортати" існуючий клас і використовувати його реалізацію всередині. Приклад цього декоративного малюнка .

Спадкування - це не дуже вдалий спосіб вирішення сценаріїв повторного використання, оскільки часто потрібно використовувати лише частину функціональності базового класу, а підклас не може підтримувати весь контракт базового класу таким чином, щоб задовольнити заміну Ліскова принцип .

Метод шаблонів - хороший приклад випадку, коли успадкування є доцільним.

Дивіться цю відповідь для рекомендацій щодо вибору між складом та спадщиною.


+1 для того, щоб вказати, що у разі часткового повторного використання класу невикористану частину більш чисто ігнорують склад, ніж при спадкуванні.
Лоуренс

4

Я недостатньо знайомий з PHP, щоб навести конкретні приклади на цій мові, але ось кілька рекомендацій, які я вважаю корисними.

Інтерфейси / риси корисні для визначення поведінки в багатьох класах, які можуть мати дуже мало спільного.

Наприклад, якщо ви працюєте над api JSON, ви можете визначити інтерфейс, який визначає два способи: "toJson" та "toStatusCode". Це дозволить вам написати помічник, який може приймати будь-який об’єкт, що реалізує цей інтерфейс, і перетворити його у відповідь Http.

Здебільшого це є переважним, ніж пряме спадкування, оскільки менше шансів постраждати від крихкої проблеми базового класу, оскільки він схильний до ієрархій мілководних класів.

Пряме успадкування найкраще розглядати як щось, що ви робите, коли для цього є вагомі причини, а не те, що ви робите, якщо немає вагомих причин цього не робити.

У мовах, які не підтримують реалізацію за замовчуванням в інтерфейсах, це може бути розумним способом поділитися набором базових функціональних можливостей, але зазвичай сильно обмежує те, що можна робити з підкласами (багатократне успадковування, коли воно доступне, рідко коштує болю) . Часто мені здається, що варто написати біль, як писати методи перенаправлення котлована, щоб замість цього використовувати композицію.

Склад повинен бути вашою стратегією за замовчуванням. Наскільки можливо, складіть інтерфейси та передайте їх як аргументи конструктора. Це значно полегшить ваше життя при написанні тестів.

Уникайте максимальної роботи з глобальними синглтонами, оскільки порівняння очікуваного та фактичного виходу є правильним болем, якщо частина виводу залежить від стану системного годинника.

Це, звичайно, не завжди можливо. Наприклад, веб-рамка Play пропонує бібліотеку JSON, яка в основному є мовою, що залежить від домену. Зміна цього було б головним завданням, з якого заміна дзвінків на "Json.prettyPrint" буде лише дуже незначною частиною.


1

Композиція - це коли клас пропонує певну функціональність, інстанціюючи (можливо, внутрішній) клас, який вже реалізує цю функціональність, а не успадковуючи цей клас.

Так, наприклад, якщо у вас є клас, який моделює судно, і вам зараз кажуть, що ваш корабель повинен запропонувати вертоліт, не природним є виведення вашого корабля з вертольота, (так!) Замість цього, ви повинні мати ваш корабель містить клас вертольотів і виставляє його деяким Ship.getHelipad()методом.

У роки (десять років тому) люди звикли сприймати спадщину як швидкий і простий спосіб об'єднання функціональних можливостей, тому було багато прикладів "судна, що успадковували з вертолітної майданчика", які, звичайно, були дуже кульгавими.

Але вираз «Склади прихильності над успадкуванням» був ретельно сформульований, щоб зрозуміти, що це лише пропозиція, а не правило. Автор видання був досить обережним, щоб утриматися від того, щоб сказати щось на кшталт "ти ніколи не будеш використовувати спадщину, лише склад". Це, в основному, доводить до відома спільноти інженерних програмних послуг той факт, що надбання надмірно використовувалося, в той час як у багатьох випадках композиція створює більш чіткі, елегантні та бездоганний дизайн, ніж спадкування.

Отже, по суті, висловлювання «Улюблений склад над спадщиною» говорить про те, що кожного разу, коли ви зіткнетесь із «спадкувати чи складати»? Питання, вам варто задуматися над тим, яка стратегія є найбільш підходящою, і що найбільш шанси на те, що найбільш підходяща стратегія виявиться складом, а не спадщиною.

Але оскільки це не правило, слід також пам’ятати, що є багато випадків, коли успадкування є більш природним. Якщо ви використовуєте склад там, де вам слід було скористатися спадщиною, на ваш код потрапить багато зла.

Щоб повернутися до прикладу корабля, якщо вашому кораблю потрібно запропонувати інтерфейс для взаємодії з a FloatingMachine, то більш природним є його отримання від абстрактного FloatingMachineкласу, який, можливо, в свою чергу може бути похідним від іншого абстрактного Machineкласу.

Ось головне правило для відповіді на питання про склад та спадщину:

Чи є у мого класу відношення «є» до інтерфейсу, який він повинен виставити? Якщо так, використовуйте спадщину. Якщо ні, використовуйте склад.

Корабель "- це" плаваюча машина, а плаваюча машина "-" машина. Отже, успадкування цілком чудово для них. Але корабель - це, звичайно, не вертоліт. Тож краще скласти функціональність вертольота.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.