День двести сорок девятый. #BestPractices
Разработка для Расширяемости
Запечатывание
Одной из особенностей объектно-ориентированных фреймворков является то, что пользователи могут расширять и изменять их способами, не предусмотренными разработчиками фреймворка. Это и сила, и опасность расширяемого дизайна. Поэтому, когда вы разрабатываете свой фреймворк, очень важно тщательно продумать возможности расширения, где это необходимо, и ограничить возможности расширения, где оно опасно.
Мощный механизм, препятствующий расширению – это запечатывание (sealing). Вы можете запечатать класс или отдельные его члены. Запечатывание класса препятствует наследованию от класса. Запечатывание члена предотвращает переопределение пользователями отдельного члена.
❌ ИЗБЕГАЙТЕ запечатывания класса, не имея на то веских причин. Запечатывание класса только по причине того, что вы не можете придумать сценарии его расширяемости, - это плохая идея. Пользователи фреймворков любят наследовать от классов по разным неочевидным причинам, например, добавляя удобные члены, такие как пользовательские конструкторы, новые методы или перегрузки методов. Например,
Классы по умолчанию незапечатаны в большинстве языков программирования, и это также рекомендованное поведение по умолчанию для большинства классов фреймворков. Расширяемость, предоставляемая незапечатанными типами, высоко ценится пользователями фреймворка и она довольно недорогая в обслуживании из-за относительно низких затрат на тестирование.
Хорошие причины для запечатывания класса:
- Класс является статическим классом (см. https://t.me/NetDeveloperDiary/241)
- Класс хранит чувствительный к безопасности код в унаследованных защищенных членах.
- Класс наследует много виртуальных членов, и стоимость запечатывания которых по одиночке превышает преимущества от оставления класса незапечатанным.
- Класс является атрибутом, который требуется очень быстро находить во время выполнения. Запечатанные атрибуты имеют немного более высокую производительность, чем незапечатанные.
❌ ИЗБЕГАЙТЕ объявления защищённых или виртуальных членов в запечатанных типах. По определению от запечатанных типов нельзя унаследовать. Это означает, что защищённые члены запечатанных типов не могут быть вызваны, а виртуальные методы запечатанных типов не могут быть переопределены.
⚠️ РАССМОТРИТЕ возможность запечатать члены класса, которые вы переопределяете. Проблемы, которые могут возникнуть в результате введения виртуальных членов (см. https://t.me/NetDeveloperDiary/286), также применимы к переопределениям, хотя и в несколько меньшей степени. Запечатывание переопределения защищает вас от этих проблем, начиная с этой точки в иерархии наследования.
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/
Разработка для Расширяемости
Запечатывание
Одной из особенностей объектно-ориентированных фреймворков является то, что пользователи могут расширять и изменять их способами, не предусмотренными разработчиками фреймворка. Это и сила, и опасность расширяемого дизайна. Поэтому, когда вы разрабатываете свой фреймворк, очень важно тщательно продумать возможности расширения, где это необходимо, и ограничить возможности расширения, где оно опасно.
Мощный механизм, препятствующий расширению – это запечатывание (sealing). Вы можете запечатать класс или отдельные его члены. Запечатывание класса препятствует наследованию от класса. Запечатывание члена предотвращает переопределение пользователями отдельного члена.
❌ ИЗБЕГАЙТЕ запечатывания класса, не имея на то веских причин. Запечатывание класса только по причине того, что вы не можете придумать сценарии его расширяемости, - это плохая идея. Пользователи фреймворков любят наследовать от классов по разным неочевидным причинам, например, добавляя удобные члены, такие как пользовательские конструкторы, новые методы или перегрузки методов. Например,
System.Messaging.MessageQueue
является незапечатанным и, таким образом, позволяет пользователям создавать настраиваемые очереди, имеющие путь по умолчанию, или добавлять настраиваемые методы, которые упрощают API в определенных сценариях.Классы по умолчанию незапечатаны в большинстве языков программирования, и это также рекомендованное поведение по умолчанию для большинства классов фреймворков. Расширяемость, предоставляемая незапечатанными типами, высоко ценится пользователями фреймворка и она довольно недорогая в обслуживании из-за относительно низких затрат на тестирование.
Хорошие причины для запечатывания класса:
- Класс является статическим классом (см. https://t.me/NetDeveloperDiary/241)
- Класс хранит чувствительный к безопасности код в унаследованных защищенных членах.
- Класс наследует много виртуальных членов, и стоимость запечатывания которых по одиночке превышает преимущества от оставления класса незапечатанным.
- Класс является атрибутом, который требуется очень быстро находить во время выполнения. Запечатанные атрибуты имеют немного более высокую производительность, чем незапечатанные.
❌ ИЗБЕГАЙТЕ объявления защищённых или виртуальных членов в запечатанных типах. По определению от запечатанных типов нельзя унаследовать. Это означает, что защищённые члены запечатанных типов не могут быть вызваны, а виртуальные методы запечатанных типов не могут быть переопределены.
⚠️ РАССМОТРИТЕ возможность запечатать члены класса, которые вы переопределяете. Проблемы, которые могут возникнуть в результате введения виртуальных членов (см. https://t.me/NetDeveloperDiary/286), также применимы к переопределениям, хотя и в несколько меньшей степени. Запечатывание переопределения защищает вас от этих проблем, начиная с этой точки в иерархии наследования.
Источник: https://docs.microsoft.com/en-us/dotnet/standard/design-guidelines/