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


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.