Чому використання «фіналу» на уроці дійсно так погано?


34

Я перероблю застарілий веб-сайт PHP OOP.

Мене так спокушає почати використовувати "фінал" на заняттях до " make it explicit that the class is currently not extended by anything". Це може заощадити багато часу, якщо я приходжу до класу, і мені цікаво, чи можу я перейменувати / видалити / змінити protectedвластивість чи метод. Якщо я дійсно хочу розширити клас, я можу просто видалити остаточне ключове слово, щоб розблокувати його для розширення.

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

Це все здається розумною ідеєю економії часу .... але я часто читав, що заняття слід робити "остаточними" лише в рідкісних / особливих випадках.

Можливо, це накручує створення об'єкта Mock або має інші побічні ефекти, про які я не думаю.

Що я пропускаю?


3
Якщо ви повністю контролюєте свій код, то, мабуть, це не страшно, але трохи на стороні OCD. Що робити, якщо ви пропустите заняття (наприклад, у нього немає дітей, але це фінал)? Краще дозвольте інструменту сканувати код і повідомити, чи є в деяких класах дітей. Як тільки ви упакуєте це як бібліотеку і передаєте його комусь іншому, розгляд «остаточного» стає болем.
Робота

Наприклад, MbUnit - це відкрита рамка для тестування .Net-одиниць, а MsTest - не (сюрприз-сюрприз). Як результат, у вас є оболонка 5k на MSFT тільки тому, що ви хочете виконати деякі тести MSFT способом. Інші тестові бігуни не можуть запустити тестовий dll, побудований за допомогою MsTest - все через кінцеві класи, які використовував MSFT.
Робота

3
Деякі дописи на тему: Підсумкові класи: Відкриті для продовження, закриті для спадкування (травень 2014; Матіас Верраес)
hakre

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

"PHP 5 вводить остаточне ключове слово, яке заважає дочірнім класам змінити метод шляхом префіксації визначення остаточним. Якщо сам клас визначається остаточним, його не можна продовжувати." php.net/manual/en/language.oop5.final.php Це зовсім не погано.
Чорний

Відповіді:


54

Я часто читав, що заняття слід робити «підсумковими» лише в рідкісних / особливих випадках.

Хто написав, це неправильно. Використовуйте finalвільно, в цьому немає нічого поганого. Це документи, що клас не був розроблений з урахуванням спадкування, і це, як правило, справедливо для всіх класів за замовчуванням: проектування класу, який може бути успадковано за значущою, займає більше, ніж просто видалення finalспецифікатора; це потребує великої турботи.

Тож використання finalза замовчуванням аж ніяк не є поганим. Насправді багато людей пропонують, що це має бути типовим, наприклад, Джон Скіт .

Можливо, це відкручує створення об'єкта Mock ...

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


1
1+: оскільки він зкручує насмішки; розглянути можливість створення інтерфейсів або абстрактних класів для кожного "остаточного" класу.
Спойк

Добре, що додає гайковий ключ у творах. Цей пізній прихід суперечить усім іншим відповідям. Тепер я не знаю, у що вірити: о. (Увімкнув мою прийняту відповідь і відклав рішення під час повторного судового розгляду)
JW01

1
Ви можете розглянути сам Java API і як часто ви знайдете використання "остаточного" ключового слова. Насправді він використовується не дуже часто. Він використовується в тих випадках, коли продуктивність може погіршитися через динамічну прив'язку, яка відбувається при виклику "віртуальних" методів (у Java всі методи віртуальні, якщо вони не оголошені як "остаточні" або "приватні") або при введенні користувацьких підкласи класу Java API можуть виникнути проблеми узгодженості, такі як підкласифікація 'java.lang.String'. Рядок Java - це об'єкт значення за визначенням і не повинен згодом змінювати його значення. Підклас може порушити це правило.
Джоні Ді

5
@Jonny Більшість API Java погано розроблені. Пам'ятайте, що основній частині цього віку більше десяти років, а тим часом було розроблено багато найкращих практик. Але не візьміть на це моє слово: самі інженери Java говорять про це, в першу чергу Джоша Блоха, який детально написав про це, наприклад, в « Ефективна Java» . Будьте впевнені, що якби Java API розроблявся сьогодні, він виглядав би зовсім інакше і finalзіграв би набагато більшу роль.
Конрад Рудольф

1
Я досі не впевнений, що використання «фіналу» частіше стає найкращою практикою. На мою думку, «остаточний» справді слід використовувати лише в тому випадку, якщо вимоги до продуктивності його диктують, або якщо ви впевнені, що послідовність не може бути порушена підкласами. У всіх інших випадках необхідна документація повинна входити в документацію (яку все-таки потрібно писати), а не у вихідний код як ключові слова, що застосовують певні шаблони використання. Для мене це такий собі бог, що грає. Використання приміток може бути кращим рішенням. Можна додати анотацію '@final', і компілятор міг видати попередження.
Джонні Ді

8

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


17
Це абсолютно помилково! "Використання мовних ключових слів просто для того, щоб щось вам сигналізувати [...] - погана ідея". - Навпаки, саме для цього використовуються мовні ключові слова. Ваш попередній застереження "і тільки ви коли-небудь дізнаєтесь, що це означає", звичайно, правильний, але тут не застосовується; ОП використовує, finalяк очікувалося. У цьому немає нічого поганого. І використання мовної функції для забезпечення обмеження завжди перевершує використання коментаря.
Конрад Рудольф

8
@Konrad: За винятком випадків, finalколи "підклас цього класу ніколи не повинен створюватися" (з юридичних причин або щось подібне), не "цей клас наразі не має дітей, тому я все ще в безпеці возитися з його захищеними членами". Наміром finalє сама антитеза "вільно редагованих", і finalклас навіть не повинен мати protectedчленів!
СФ.

3
@Konrad Rudolph Це зовсім не мізер. Якщо пізніше інші хочуть розширити його заняття, існує велика різниця між коментарем, який говорить "не розширено", і ключовим словом, яке говорить "може не поширюватися".
Джеремі

2
@Konrad Rudolph Це звучить як чудова думка, щоб викликати питання про дизайн мови OOP. Але ми знаємо, як працює PHP, і застосовується інший підхід до розширення, якщо їх не запечатано. Прикидаючись, що нормально поєднувати ключові слова, тому що "так має бути" - це просто захисність.
Джеремі

3
@Jeremy Я не плутаю ключові слова. Ключове слово finalозначає: "цей клас не слід поширювати [поки що]". Нічого, ні менш. Якщо PHP був розроблений з цією філософією в вигляді не має ніякого значення: він має на finalключове слово, в кінці кінців. По-друге, аргументація дизайну PHP не може зазнати невдачі, враховуючи, наскільки печворк та просто загальний дизайн PHP.
Конрад Рудольф

5

Є приємна стаття про те, «Коли оголосити класи остаточними» . Кілька цитат з нього:

TL; DR: Зробіть свої класи завжди final, якщо вони реалізують інтерфейс, а інші публічні методи не визначені

Навіщо мені користуватися final?

  1. Запобігання масового спадкового ланцюгу приреченості
  2. Підбадьорлива композиція
  3. Примушуйте розробника думати про публічний API користувача
  4. Примушуйте розробника скорочувати відкритий API об’єкта
  5. finalКлас завжди може бути розширюваним
  6. extends порушує інкапсуляцію
  7. Вам не потрібна така гнучкість
  8. Ви можете вільно змінити код

Коли уникати final:

Підсумкові заняття працюють ефективно лише за таких припущень:

  1. Існує абстракція (інтерфейс), яку реалізує заключний клас
  2. Весь відкритий API остаточного класу є частиною цього інтерфейсу

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

PS Дякую @ocramius за чудове читання!


5

"заключний" для класу означає: Ви хочете підклас? Вперед, видаліть "остаточний" підклас стільки, скільки вам подобається, але не скаржтеся на мене, якщо він не працює. Ви самі по собі.

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


-1

Одне, про що ви, можливо, не подумали, - це той факт, що будь-яка зміна класу означає, що він повинен пройти новий QA-тестування.

Не позначайте речі як остаточні, якщо ви насправді це не маєте на увазі.


Спасибі. Чи можете ви пояснити це далі. Ви маєте на увазі, що якщо я позначаю клас як final(зміну), то мені просто доведеться його повторно перевірити?
JW01

4
Я б сказав це сильніше. Не позначайте речі як кінцеві, якщо розширення не може бути змушене працювати через дизайн класу.
S.Lott

Спасибі С.Лотт - я намагаюся зрозуміти, як погане використання фіналу впливає на код / ​​проект. Чи ви пережили якісь погані історії жахів, які призвели до того, що ви настільки догматичні щодо щадного використання final. Це досвід з перших рук?
JW01

1
@ JW01: finalКлас має один випадок основного використання. У вас є поліморфні класи, які ви не хочете розширювати, оскільки підклас може порушити поліморфізм. Не використовуйте, finalякщо ви не повинні перешкоджати створенню підкласів. Крім цього, це марно.
S.Lott

@ JW01, в умовах виробництва може не бути ДОЗВОЛЕНО , щоб видалити остаточний, тому що це не код протестований клієнт і затверджений. Однак вам можуть дозволити додавати додатковий код (для тестування), але ви, можливо, не зможете робити те, що хочете, тому що не можете розширити свій клас.

-1

Використання "final" позбавляє свободу інших, хто хоче використовувати ваш код.

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

Також часто є код, який краще не робити private, але protected. Звичайно, privateозначає "капсулювання" та приховування речей, що вважаються деталями реалізації. Але як програміст API я можу також задокументувати той факт, що метод xyzвважається деталі реалізації, і, таким чином, він може бути змінений / видалений у наступній версії. Тож кожен, хто покладається на такий код, незважаючи на попередження, робить це на свій страх і ризик. Але він насправді може це зробити і використовувати повторно (сподіваємось, вже перевірений) код і швидше придумати рішення.

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

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

Зауважте, що я не вважаю «остаточним» або «приватним» злим, але думаю, що вони використовуються занадто часто, оскільки програміст не замислювався над своїм кодом з точки зору повторного використання та розширення коду.


2
"Успадкування - це не повторне використання коду".
Quant_dev

2
Гаразд, якщо ви наполягаєте на ідеальному визначенні, ви маєте рацію. Спадщина виражає "є-а" відносини, і саме цей факт дозволяє поліморфізм і забезпечує спосіб розширення API. Але на практиці спадкування дуже часто використовується для повторного використання коду. Особливо це стосується багатократного успадкування. І як тільки ви називаєте код суперкласу, ви фактично використовуєте код.
Джоні Ді

@quant_dev: Повторність використання OOP означає насамперед поліморфію. І успадкування є критичною точкою для поліморфної поведінки (обмін інтерфейсом).
Сокіл

Інтерфейс обміну @Falcon! = Код спільного використання. Принаймні, не у звичному розумінні.
Quant_dev

3
@quant_dev: Ви отримуєте повторне використання у розумінні OOP неправильно. Це означає, що один метод може працювати на багатьох типах даних завдяки спільному доступу до інтерфейсу. Це основна частина повторного використання коду OOP. І успадкування автоматично дасть нам спільний доступ до інтерфейсів, тим самим значно підвищивши повторне використання коду. Тому ми говоримо про повторне використання в OOP. Просто створення бібліотек підпрограми майже настільки ж давно, як і саме програмування, і їх можна робити майже на будь-якій мові, це загальноприйнято. Повторне використання коду в ООП більше.
Сокіл
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.