Должен ли я рекомендовать классы запечатывания по умолчанию? - PullRequest
63 голосов
/ 17 июня 2011

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

Я нахожу странным, что в java и c # классифицируются как неопечатанные / не финальные pr по умолчанию.Я думаю, что закрытые классы значительно улучшают читабельность кода.

Обратите внимание, что это внутренний код, который мы всегда можем изменить в случае редкого случая, когда нам нужно создать подкласс.

Каковы вашиопыт?Я встречаю некоторое сопротивление этой идее.Неужели люди, которым лень их не удосужиться напечатать, «запечатаны»?

Ответы [ 13 ]

60 голосов
/ 17 июня 2011

Хорошо, как и многие другие люди ...

Да, я думаю, что вполне разумно рекомендовать закрывать классы по умолчанию.

Это соответствует рекомендации Джоша Блоха в его превосходной книге Effective Java, 2-е издание :

Создай наследство или запрети его.

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

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

Далее следует неизменность - мне нравятся неизменные типы. Я считаю, что их легче рассуждать, чем изменяемые типы. Это одна из причин, почему API Joda Time лучше, чем использование Date и Calendar в Java. Но открытый класс никогда не может быть известным как 1032 * неизменным. Если я принимаю параметр типа Foo, я могу полагаться на свойства , объявленные в Foo, которые не могут быть изменены с течением времени, но я не могу полагаться на то, что сам объект не будет изменено - в подклассе может быть изменяемое свойство. Небеса, помогите мне, если это свойство также используется переопределением некоторого виртуального метода. Волна прощается со многими преимуществами неизменности. (По иронии судьбы, Joda Time имеет очень большие иерархии наследования - часто с такими вещами, как «подклассы должны быть неизменными. Большая иерархия наследования Chronology затруднила понимание при портировании на C #.)

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

РЕДАКТИРОВАТЬ: Я также хотел бы указать читателям на сообщение в блоге Эрика Липперта от 2004 года о том, почему так много классов платформы запечатаны. Есть много мест, где я бы хотел, чтобы .NET предоставил интерфейс , с которым мы могли бы работать для тестируемости, но это немного другой запрос ...

20 голосов
/ 17 июня 2011

По моему мнению, архитектурные решения принимаются для того, чтобы сообщить другим разработчикам (в том числе будущим разработчикам) что-то важное.

Классы запечатывания сообщают, что реализация не должна быть переопределена. Это говорит о том, что класс не должен выдавать себя за другого. Есть веские причины для печати. ​​

Если вы применяете необычный подход к запечатыванию всего (и это необычно), то ваши проектные решения теперь сообщают о вещах, которые действительно не важны - например, класс не предназначен для наследования оригинальный / авторский разработчик.

Но тогда как бы вы сообщили другим разработчикам, что класс не должен наследоваться из-за чего-то? Вы действительно не можете. Вы застряли.

Кроме того, запечатывание класса не улучшает читабельность. Я просто не вижу этого. Если наследование является проблемой при разработке ООП, то у нас есть гораздо большая проблема.

9 голосов
/ 17 июня 2011

Я хотел бы думать, что я достаточно опытный программист, и, если я ничему не научился, так это то, что я удивительно плохо предсказываю будущее.

Печатание в запечатанном виде не сложно, я просто не хочу раздражать разработчика в будущем (который может быть мной!), Который обнаруживает, что проблема может быть легко решена с небольшим количеством наследства.

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

5 голосов
/ 17 июня 2011

Не должно быть ничего плохого в наследовании от класса.

Вы должны запечатать класс только тогда, когда есть веская причина, по которой он никогда не должен наследоваться.

Кроме того, если вы запечатаете их все, это только уменьшит ремонтопригодность. Каждый раз, когда кто-то захочет унаследовать от одного из ваших классов, он увидит, что он запечатан, затем он либо снимет печать (путаница с кодом, с которой ему не придется возиться), либо еще хуже: создайте плохую реализацию вашего класс для себя.

Тогда у вас будет 2 реализации одной и той же вещи, одна из которых, вероятно, хуже другой, и 2 части кода для поддержки.

Лучше просто держать его открытым. Никакого вреда в том, что он распечатан.

4 голосов
/ 17 июня 2011

Честно говоря, я думаю, что классы, не закрываемые по умолчанию в c #, немного странны и неуместны с тем, как остальные языковые настройки работают в языке.

По умолчанию классы internal.По умолчанию поля private.По умолчанию членами являются private.

Кажется, что существует тенденция, которая указывает на наименее вероятный доступ по умолчанию.Само собой разумеется, что ключевое слово unsealed должно выходить в c # вместо sealed.

Лично я бы предпочел, чтобы классы были запечатаны по умолчанию.В большинстве случаев, когда кто-то пишет класс, он не разрабатывает его с учетом подклассов и всех сложностей, которые возникают вместе с ним.Проектирование для будущего подкласса должно быть сознательным действием, и поэтому я бы предпочел, чтобы вы явно заявили об этом.

3 голосов
/ 07 июля 2011

© Джеффри Рихтер

Существует три причины, по которым закрытый класс лучше, чем открытый класс:

  • Версия : если класс изначально запечатан, он может измениться на незапечатанный в будущем, не нарушая совместимость.Однако, как только класс распечатан, вы никогда не сможете изменить его на закрытый в будущем, так как это нарушит все производные классы.Кроме того, если незапечатанный класс определяет какие-либо незапечатанные виртуальные методы, порядок вызовов виртуальных методов должен поддерживаться с новыми версиями, иначе существует вероятность нарушения производных типов в будущем.
  • Производительность : Как обсуждалось в предыдущем разделе, вызов виртуального метода не так эффективен, как вызов невиртуального метода, поскольку CLR должен искать тип объекта во время выполнения, чтобы определить, какой тип определяет метод для вызова.Однако, если JIT-компилятор видит вызов виртуального метода с использованием закрытого типа, JIT-компилятор может создавать более эффективный код, вызывая метод не виртуально.Он может сделать это, потому что знает, что не может быть производного класса, если класс запечатан.
  • Безопасность : и предсказуемость Класс должен защищать свое собственное состояние и не позволять себекогда-нибудь станет испорченным.Когда класс незапечатан, производный класс может обращаться к состоянию базового класса и манипулировать им, если какие-либо поля данных или методы, которые внутренне манипулируют полями, доступны и не являются закрытыми.Кроме того, виртуальный метод может быть переопределен производным классом, и производный класс может решить, вызывать ли реализацию базового класса.Делая метод, свойство или событие виртуальным, базовый класс отказывается от некоторого контроля над своим поведением и своим состоянием.Если это не будет тщательно продумано, это может привести к непредсказуемому поведению объекта и открыть потенциальные дыры в безопасности.
2 голосов
/ 17 июня 2011

«... подумайте, как их классы должны быть подклассифицированы ...» не имеет значения.

По крайней мере, полдюжины раз за последние несколько лет я проклинал какую-то команду с открытым исходным кодом или другую за неаккуратное сочетание защищенных и приватных, делая невозможным простое расширение класса без копирования источника всего родительский класс. (В большинстве случаев для переопределения определенного метода требуется доступ к закрытым членам.)

Одним из примеров был тег JSTL, который почти делал то, что я хотел. Мне нужно переопределить одну маленькую вещь. Нет, извините, мне пришлось полностью скопировать источник родителя.

2 голосов
/ 17 июня 2011

Это очень самоуверенный вопрос, который, вероятно, соберет некоторые очень самоуверенные ответы; -)

Тем не менее, по моему мнению, я настоятельно предпочитаю НЕ делать свои уроки запечатанными / окончательными, особенно в начале.Это делает очень трудным вывод предполагаемых точек расширяемости, и почти невозможно получить их прямо в начале.ИМХО, чрезмерное использование инкапсуляции хуже, чем чрезмерное использование полиморфизма.

2 голосов
/ 17 июня 2011

Я запечатываю классы, только если я работаю над повторно используемым компонентом, который я собираюсь распространять, и я не хочу, чтобы конечный пользователь наследовал от него, или как системный архитектор, если я знаю, что не хочу другого разработчикав команде, чтобы унаследовать от него.Однако для этого обычно есть какая-то причина.

Только потому, что класс не наследуется, я не думаю, что он должен автоматически помечаться как запечатанный.Кроме того, меня бесконечно раздражает, когда я хочу сделать что-то хитрое в .NET, но потом понимаю, что MS отмечает множество своих классов запечатанных.

2 голосов
/ 17 июня 2011

Мне не нравится этот способ мышления. Java и c # созданы как языки ООП. Эти языки разработаны таким образом, чтобы у класса мог быть родитель или ребенок. Вот и все.

Некоторые люди говорят, что мы всегда должны начинать с самого ограничивающего модификатора (приватный, защищенный ...) и устанавливать ваш член на общедоступный, только когда вы используете его извне. Эти люди, для меня, ленивы и не хотят думать о хорошем дизайне в начале проекта.

Мой ответ таков: создавайте свои приложения хорошо сейчас. Установите свой класс на печать, когда он должен быть закрытым и закрытым, когда он должен быть закрытым. Не делайте их запечатанными по умолчанию.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...