Чому слід уникати наслідування Java "Розширення"


40

- сказав Джейм Гослінг

"Ви повинні уникати успадкування впровадження, коли це можливо."

і замість цього використовувати інтерфейс успадкування.

Але чому? Як ми можемо уникнути успадкування структури об’єкта за допомогою ключового слова "extends" і водночас зробити наш код об'єктно орієнтованим?

Чи може хтось, будь ласка, навести об'єктно-орієнтований приклад, що ілюструє цю концепцію у такому сценарії, як "замовлення книги в книгарні?"



Проблема в тому, що ви можете розширити лише один клас.

Відповіді:


38

Гослінг не сказав уникати використання ключового слова extens ... він просто сказав, що не використовувати спадщину як засіб для досягнення повторного використання коду.

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

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

Прочитайте про принцип заміщення Ліскова і зрозумійте його. Щоразу, коли ви збираєтеся розширити клас (або інтерфейс з цього питання), запитайте себе, чи не порушуєте ви цей принцип, якщо так, то ви, швидше за все, (не) використовуєте спадщину неприродними способами. Це легко зробити і часто здається правильним, але це зробить ваш код складнішим у підтримці, тестуванні та розвитку.

--- Редагувати Ця відповідь дає досить хороший аргумент, щоб відповісти на питання більш загальними.


3
(Реалізація) успадкування не є важливим для створення структур ОО. Ви можете мати мову OO без нього.
Андрес Ф.

Чи можете ви навести приклад? Я ніколи не бачив ОО без спадщини.
Ньютопський

1
Проблема полягає в тому, що не існує прийнятого визначення, що таке OOP. (Убік, навіть Алан Кей пошкодував поширеного тлумачення свого визначення з його сучасним акцентом на "об'єкти", а не на "повідомлення"). Ось спроба відповіді на ваше запитання . Я згоден, рідко бачити ООП без спадщини; отже, мій аргумент був просто тим, що це не суттєво ;)
Андрес Ф.

9

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

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

Порада Гослінга (та інші твердження) начебто це була сильна медицина для досить серйозного захворювання, і можливо, деяких дітей викинули разом з водою. Немає нічого поганого в тому, щоб використовувати спадщину для моделювання відносин між класами, які справді є is-a. Але якщо ви просто хочете використовувати частину функціональності іншого класу, вам краще спочатку вивчити інші варіанти.


6

Наслідування отримало досить страшну репутацію останнім часом. Одна з причин цього уникати - це принцип дизайну "склад над успадкуванням", який, в основному, спонукає людей не користуватися спадщиною там, де це не справжнє відношення, а лише для того, щоб зберегти деякі написання коду. Цей вид успадкування може спричинити важкі пошуки і важко виправити помилки. Причина, чому ваш друг, мабуть, запропонував замість цього використовувати інтерфейс, полягає в тому, що інтерфейси забезпечують гнучкіше і рентабельніше рішення для розподілених api. Вони частіше дозволяють вносити зміни до api, не порушуючи код користувача, де експонування класів часто порушує код користувача. Найкращим прикладом цього в .Net є різниця в використанні між List та IList.


5

Тому що ви можете розширити лише один клас, тоді як ви можете реалізувати багато інтерфейсів.

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

Інтерфейси також дозволяють краще використовувати поліморфізм.

Це може бути частиною причини.


чому потік?
Махмуд Хоссам

6
Відповідь ставить візок перед конем. Java дозволяє розширити лише один клас через чітко сформульований принцип Gosling (дизайнер Java). Це як сказати, що діти повинні носити шоломи під час їзди на велосипеді, тому що це закон. Це правда, але закон і поради випливають із уникнення травм голови.
JohnMcG

@JohnMcG Я не пояснюю вибір дизайну, який зробив Гослінг, я просто даю поради кодеру, кодери, як правило, не заважають мовному дизайну.
Махмуд Хоссам

1

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

Я думаю, що це дійсно - це відокремити ваш код від конкретних реалізацій. Скажіть, у мене є клас під назвою ConsoleWriter, і він переходить в метод, щоб щось написати, і він записує його в консоль. Тепер скажемо, що хочу перейти до друку у вікно графічного інтерфейсу. Ну тепер я повинен або змінити метод, або написати новий, який приймає GUIWriter як параметр. Якщо я почав з визначення інтерфейсу IWriter і застосував метод в IWriter, я міг би почати з ConsoleWriter (який би реалізував інтерфейс IWriter), а потім пізніше написати новий клас під назвою GUIWriter (який також реалізує інтерфейс IWriter), а потім я просто довелося б вимкнути клас, що передається.

Інша річ (що стосується C #, не впевнений у Java) - це те, що ви можете розширити лише 1 клас, але реалізувати багато інтерфейсів. Скажімо, у мене були класи на ім'я Вчитель, Математика та Історія. Тепер MathTeacher та HistoryTeacher поширюються від вчителя, але що робити, якщо ми хочемо, щоб клас представляв когось, хто є і MathTeacher, і HistoryTeacher. При спробі успадкування від декількох класів це може стати досить безладним, коли ви можете це робити лише один за одним (є спосіб, але вони не зовсім оптимальні). З інтерфейсами ви можете мати два інтерфейси під назвою IMathTeacher та IHistoryTeacher, а потім мати один клас, який поширюється на вчителя та реалізує ці два інтерфейси.

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

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


2
"Ви можете розширити лише 1 клас, але реалізувати багато інтерфейсів" Це справедливо і для Java, і для C #.
FrustratedWithFormsDesigner

Одне можливе рішення проблеми дублювання коду - створити абстрактний клас, який реалізує інтерфейс і розширить його. Однак використовуйте обережно; Є лише кілька випадків, які я бачив, що це має сенс.
Майкл К

0

Якщо ви успадковуєте від класу, ви приймаєте залежність не лише від цього класу, але також маєте виконувати будь-які неявні контракти, створені клієнтами цього класу. Дядько Боб наводить чудовий приклад того, як легко тонко порушити Принцип заміни Ліскова, використовуючи основну дилему прямокутника . Інтерфейси, оскільки вони не мають реалізації, також не мають прихованих контрактів.


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