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

96

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

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

mmiika
источник

Ответы:

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

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

CVertex
источник
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
1

Sealed используется для предотвращения «проблемы хрупкого базового класса». Я нашел хорошую статью в MSDN, которая объясняет это.

ихебихеб
источник
0

Герметизация позволяет получить незначительный прирост производительности. Это менее верно в мире JIT и ленивой пессимизации, чем в мире, скажем, C ++, но поскольку .NET не так хорош, как пессимизация, как компиляторы Java, в основном из-за различных философий проектирования, он все еще полезен. Он сообщает компилятору, что он может напрямую вызывать любые виртуальные методы, а не вызывать их косвенно через vtable.

Это также важно, когда вам нужен «закрытый мир» для таких вещей, как сравнение равенства. Обычно, когда я определяю виртуальный метод, мне очень хочется определить понятие сравнения на равенство, которое действительно реализует эту идею. С другой стороны, я мог бы определить его для конкретного подкласса класса с помощью виртуального метода. Запечатывание этого класса гарантирует, что равенство действительно выполняется.

Эдвард КМЕТТ
источник
0

Запечатывание класса упрощает управление одноразовыми ресурсами.

Джефф Данлоп
источник
0

Чтобы определить, следует ли запечатать класс, метод или свойство, обычно следует учитывать следующие два момента:

• Потенциальные преимущества, которые производные классы могут получить за счет возможности настройки вашего класса.

• Возможность того, что производные классы могут изменить ваши классы таким образом, что они больше не будут работать правильно или как ожидалось.

user3629577
источник
0

Еще одно соображение заключается в том, что запечатанные классы не могут быть заглушены в ваших модульных тестах. Из документации Microsoft :

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

Дэйв Кларк
источник