Навіщо опечатувати клас?


96

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

Отже, чому фреймворк розроблений таким чином, і чи не буде непорушною зміна розпечатати все? Має бути інша причина, але просто бути злим?



Відповіді:


41
  • Іноді класи занадто дорогоцінні і не призначені для передачі у спадок.
  • Час виконання / відображення може робити припущення щодо успадкування щодо запечатаних класів при пошуку типів. Чудовим прикладом цього є - атрибути рекомендується герметизувати для швидкості виконання пошуку. type.GetCustomAttributes (typeof (MyAttribute)) буде працювати значно швидше, якщо MyAttribute буде запечатаний.

Стаття MSDN для цієї теми - Обмеження розширюваності класами герметизації .


3
Радий бачити, що вони зараз чітко говорять "використовуйте з обережністю" ... хоч би вони практикували те, що проповідують.
— mmiika

13
Мені це здається поганою порадою :(
— Джон Скіт,

4
@CVertex: Вибачте, я не намагався вас критикувати - лише стаття.
— Джон Скіт,

16
@generalt: Я вірю в оформлення спадщини або заборону цього. Проектування для успадкування вимагає досить багато роботи і часто обмежує реалізації в майбутньому. Спадщина також вводить у абонентів невизначеність щодо того, до чого саме вони будуть телефонувати. Він також не поєднується з незмінністю (до якої я прихильник). Я вважаю, що успадкування класів корисно лише у відносно невеликій кількості місць (тоді як я люблю інтерфейси).
— Джон Скіт,

1
@CVertex Якщо ви використовували .NET, ви, мабуть, зіткнулися з проблемою і просто не помітили, майже всі основні класи .NET запечатані.
— CoryG

103

Класи повинні бути призначені для успадкування або забороняти це. Існує вартість проектування на спадщину:

  • Він може закріпити вашу реалізацію (вам потрібно оголосити, які методи будуть викликати які інші методи, у випадку, якщо користувач замінює один, але не інший)
  • Це виявляє вашу реалізацію, а не лише ефекти
  • Це означає, що вам потрібно продумати більше можливостей під час проектування
  • Такі речі, як Equals, важко спроектувати в дереві успадкування
  • Це вимагає додаткової документації
  • Незмінний тип, який є підкласом, може стати змінним (ick)

Пункт 17 "Ефективної Java" детальніше про це розповідає - незалежно від того, що він написаний у контексті Java, порада стосується і .NET.

Особисто я б хотів, щоб класи були запечатані за замовчуванням у .NET.


26
Хм .. якщо ви продовжите клас, чи не ваша проблема, якщо ви його зламаєте?
— mmiika

25
Що робити, якщо зміна реалізації, над якою ви не контролюєте базовий клас, вас зламає? Чия це провина? Спадщина, в основному, призводить до крихкості. Перевага композиції перед спадщиною сприяє стійкості, IMO.
— Джон Скіт,

6
Так, інтерфейси приємні - і так, ви все одно можете схилятись до композиції. Але якщо я викрию незапечатаний базовий клас, не дуже ретельно продумуючи його, я мав би очікувати, що зміни можуть зламати похідні класи. Мені це здається поганим. Краще заклеїти клас і уникнути поломок, ІМО.
— Джон Скіт,

8
@Joan: Склад - це відносини "має", а не "є". Отже, якщо ви хочете написати клас, який може діяти як список деяким чином, але не в інших, можливо, ви захочете створити клас із змінною-членом List <T>, а не похідною від List <T>. Потім ви використовуєте список для реалізації різних методів.
— Джон Скіт,

3
@ThunderGr: Але це моя думка: коли вам не потрібно турбуватися про те, які інші реалізації передбачені підкласами, ви можете бути вільнішими за допомогою власної реалізації. Наприклад, припустимо, один метод реалізований за допомогою виклику іншого методу, і обидва вони є віртуальними. Це потрібно задокументувати - хоча це здається деталлю реалізації. В основному, дизайн спадщини додає наручники - які є доречними в одних випадках, але не в інших. Я б віддав перевагу герметичному класу, який реалізує інтерфейс, ніж негерметичному класу з купою віртуальних методів.
— Джон Скіт,

8

Здається, що офіційні вказівки Microsoft щодо ущільнення змінилися з часу, коли це питання було задано ~ 9 років тому, і вони перейшли від філософії вибору (печать за замовчуванням) до відмови (за замовчуванням не печать):

X НЕ запечатуйте класи, не маючи для цього поважних причин.

Запечатування класу, оскільки ви не можете подумати про сценарій розширюваності, не є вагомою причиною. Користувачі Framework люблять успадковувати класи з різних не очевидних причин, наприклад, додавання зручних членів. Див. Unseated Classes для прикладів неочевидних причин, які користувачі хочуть успадкувати від типу.

Вагомими причинами запечатування класу є такі:

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

X НЕ оголошуйте захищені або віртуальні члени на закритих типах.

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

✓ ВИМІТІТЕ герметизацію елементів, які ви перевизначаєте. Проблеми, які можуть виникнути внаслідок введення віртуальних членів (обговорюються у "Віртуальних членах"), стосуються і заміщення, хоча і в дещо меншій мірі. Запечатування заміни захищає вас від цих проблем, починаючи з того моменту в ієрархії успадкування.

Дійсно, якщо ви шукаєте в кодовій базі ASP.Net Core , ви знайдете лише близько 30 випадків sealed class, більшість з яких є атрибутами та тестовими класами.

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


4

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

Я не знаю, чи виступ є єдиною перевагою запечатаних класів, і особисто я також хотів би знати інші причини ...


4
Було б цікаво подивитися, про яку вигоду від продуктивності вони говорять ...
— mmiika

3

Наприклад, продуктивність є важливим фактором, наприклад, клас рядків у Java остаточний (<- запечатаний), і причиною цього є лише продуктивність. Я думаю, ще одним важливим моментом є уникнення проблеми крихкого базового класу, докладно описаної тут: http://blogs.msdn.com/ericlippert/archive/2004/01/07/virtual-methods-and-brittle-base-classes. aspx

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


Причиною того, що String у Java остаточний, є не продуктивність, це безпека.
— CesarB

@CesarB: Так, але також, String не є звичайним класом Java. Це єдиний (я вважаю) клас в Java, який підтримує перевантаження оператора (докладніше див. Тут , розділ: "Навіть C і Java мають (жорстко закодовані) перевантаження оператора"), що неможливо в звичайному класі. Через це Stringклас може навіть не бути можливим для підкласу, навіть якби він не був остаточним.
— wchargin


0

Герметизація дозволяє досягти незначних прибутків у роботі. Це менш вірно у світі JIT і ледачої песимізації, ніж у світі, скажімо, C ++, але оскільки .NET не такий хороший, як песимізація, як компілятори Java, здебільшого через різні філософії дизайну, він все ще корисний. Він повідомляє компілятору, що він може безпосередньо викликати будь-які віртуальні методи, а не побічно викликати їх через vtable.

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



0

Щоб визначити, чи слід герметизувати клас, метод або властивість, слід, як правило, врахувати наступні два моменти:

• Потенційні вигоди, які може отримати виведення класів завдяки можливості налаштування вашого класу.

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


0

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

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

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