Зачем запечатывать класс?


96

Я хотел бы услышать, какова мотивация большинства закрытых классов в структуре .Net. Какая польза от запечатывания класса? Я не могу понять, как запрет на наследование может быть полезным и, скорее всего, не единственным, кто борется с этими классами.

Итак, почему фреймворк спроектирован таким образом и разве не будет решающим изменением распечатать все? Должна быть другая причина, но просто быть злым?



Ответы:


41
  • Иногда классы слишком дороги и не предназначены для наследования.
  • Среда выполнения / Reflection может делать предположения о наследовании запечатанных классов при поиске типов. Отличный пример этого - атрибуты рекомендуется запечатать для скорости выполнения поиска. type.GetCustomAttributes (typeof (MyAttribute)) будет работать значительно быстрее, если MyAttribute запечатан.

Статья MSDN по этой теме - Ограничение расширяемости с помощью классов уплотнения .


3
Рад видеть, что теперь они ясно говорят «используйте с осторожностью» ... хотя бы они практиковали то, что проповедуют.
mmiika 06

13
Мне кажется, это плохой совет :(
Джон Скит

4
@CVertex: Простите, я не пытался критиковать вас - просто статья.
Джон Скит

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

1
@CVertex Если вы использовали .NET, вы, вероятно, столкнулись с проблемой и просто не заметили, почти все основные классы .NET запечатаны.
CoryG

105

Классы должны быть либо предназначены для наследования, либо запрещать его. За проектирование для наследования нужно платить:

  • Он может закрепить вашу реализацию (вы должны объявить, какие методы будут вызывать какие другие методы, в случае, если пользователь переопределяет один, но не другой)
  • Он показывает вашу реализацию, а не только эффекты
  • Это означает, что вы должны думать о большем количестве возможностей при проектировании
  • Такие вещи, как Equals, сложно спроектировать в дереве наследования
  • Требуется дополнительная документация
  • Неизменяемый тип, который является подклассом, может стать изменяемым (ick)

В пункте 17 «Эффективной Java» содержится более подробная информация об этом - независимо от того, что он написан в контексте Java, совет применим также и к .NET.

Лично я хочу, чтобы классы были запечатаны по умолчанию в .NET.


26
Хм .. если вы расширяете класс, разве это не ваша проблема, если вы его сломаете?
mmiika 06

25
Что, если изменение реализации, которое вы не можете контролировать в базовом классе, сломает вас? Чья это вина? Наследование в основном вносит хрупкость. Предпочтение композиции перед наследованием способствует устойчивости, ИМО.
Джон Скит

6
Да, интерфейсы хороши - и да, вы все равно можете отдать предпочтение композиции. Но если я выставляю незапечатанный базовый класс, не задумываясь об этом очень тщательно, я должен ожидать, что изменения вполне могут нарушить производные классы. Мне это кажется плохим. Лучше запечатать класс и избежать поломки, IMO.
Джон Скит,

8
@Joan: Композиция - это скорее отношения типа «есть-а», чем «есть-а». Поэтому, если вы хотите написать класс, который в некоторых отношениях может действовать как список, но не в других, вы можете создать класс с переменной-членом List <T>, а не производным от List <T>. Затем вы использовали бы список для реализации различных методов.
Джон Скит,

3
@ThunderGr: Но это моя точка зрения: когда вам не нужно беспокоиться о том, какие другие реализации предоставляются подклассами, вы можете быть свободнее с вашей собственной реализацией. Например, предположим, что один метод реализуется путем вызова другого метода, и оба являются виртуальными. Это необходимо задокументировать - хотя кажется, что это деталь реализации. По сути, проектирование с учетом наследования добавляет наручники, которые подходят в некоторых случаях, но не подходят для других. Я бы предпочел запечатанный класс, реализующий интерфейс, чем незапечатанный класс с кучей виртуальных методов.
Джон Скит

8

Похоже, что официальные руководящие принципы Microsoft по запечатыванию эволюционировали с тех пор, как этот вопрос был задан ~ 9 лет назад, и они перешли от философии согласия (печать по умолчанию) к отказу (по умолчанию не запечатывать):

X НЕ закрывайте классы без уважительной причины.

Запечатывание класса из-за того, что вы не можете придумать сценарий расширения, не является хорошей причиной. Пользователи фреймворка любят наследовать классы по разным неочевидным причинам, например, добавляя удобные члены. См. В разделе «Незапечатанные классы» примеры неочевидных причин, по которым пользователи хотят наследовать от типа.

К веским причинам для запечатывания класса относятся следующие:

  • Класс - это статический класс. См. Разработка статического класса.
  • Класс хранит чувствительные к безопасности секреты в унаследованных защищенных членах.
  • Класс наследует множество виртуальных членов, и стоимость их индивидуального запечатывания перевесит выгоды от оставления класса незапечатанным.
  • Класс - это атрибут, который требует очень быстрого поиска во время выполнения. Запечатанные атрибуты имеют несколько более высокие уровни производительности, чем незапечатанные. См. Атрибуты.

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.