Почему вы должны предотвратить подкласс класса? - PullRequest
8 голосов
/ 24 августа 2008

Какие могут быть причины, препятствующие наследованию класса? (например, используя запечатанный на классе C #) Прямо сейчас я не могу думать ни о чем.

Ответы [ 14 ]

21 голосов
/ 24 августа 2008

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

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

9 голосов
/ 24 августа 2008

Как насчет того, если вы еще не уверены в интерфейсе и не хотите какой-либо другой код в зависимости от существующего интерфейса? [Это не в моей голове, но меня будут интересовать и другие причины!]

Edit: Немного поиска в Google дал следующее: http://codebetter.com/blogs/patricksmacchia/archive/2008/01/05/rambling-on-the-sealed-keyword.aspx

Цитирование:

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

  • Управление версиями : когда класс изначально запечатан, он может измениться на незапечатанный в будущем без нарушения совместимости. (...)
  • Производительность : (…) если JIT-компилятор видит вызов виртуального метода с использованием закрытых типов, JIT-компилятор может создавать более эффективный код, вызывая метод не виртуально. (…)
  • Безопасность и предсказуемость : класс должен защищать свое собственное состояние и не допускать его повреждения. Когда класс незапечатан, производный класс может обращаться к состоянию базового класса и манипулировать им, если какие-либо поля данных или методы, которые внутренне манипулируют полями, доступны и не являются закрытыми. (…)
8 голосов
/ 24 августа 2008

Я хочу дать вам это сообщение от "Код завершен":

Наследование - подклассы - имеет тенденцию к работать против основного технического как программист, которая заключается в управлении сложностью. Ради управления сложностью вы должны сохранять сильное предубеждение против наследования.

2 голосов
/ 24 августа 2008

Единственное законное использование наследования - это определение конкретного случая базового класса, например, когда наследуется от Shape для получения Circle. Чтобы проверить этот взгляд на отношение в противоположном направлении: является ли форма обобщением круга? Если ответ «да», тогда можно использовать наследование.

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

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

Более конкретный пример - шаблон Singleton. Вам нужно запечатать синглтон, чтобы не допустить нарушения "синглтона".

1 голос
/ 05 сентября 2008

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

Это хорошо сочетается с шаблоном разработки Template Method. У меня есть интерфейс с надписью «Я выполняю эту услугу». Затем у меня есть класс, который реализует этот интерфейс. Но что, если выполнение этого сервиса зависит от контекста, о котором базовый класс не знает (и не должен знать)? Что происходит, так это то, что базовый класс предоставляет виртуальные методы, которые являются либо защищенными, либо частными, и эти виртуальные методы являются перехватчиками для подклассов, чтобы предоставить часть информации или действия, которые базовый класс не знает и не может знать. Между тем, базовый класс может содержать код, общий для всех дочерних классов. Эти подклассы будут запечатаны, потому что они предназначены для выполнения одной и только одной конкретной реализации службы.

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

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

1 голос
/ 24 августа 2008

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

В качестве идеального примера я недавно создал небольшой пакет (Java, не C #, но те же принципы), чтобы обернуть функциональность вокруг инструмента memcached. Я хотел интерфейс, чтобы в тестах я мог смоделировать клиентский API memcached, который я использовал, а также чтобы мы могли переключать клиентов, если возникнет такая необходимость (на домашней странице memcached есть 2 клиента). Кроме того, я хотел иметь возможность полностью заменить функциональность, если возникла необходимость или желание (например, если серверы memcached по какой-то причине не работают, мы могли бы вместо этого выполнить горячую замену с реализацией локального кэша).

Я представил минимальный интерфейс для взаимодействия с клиентским API, и было бы замечательно расширить класс клиентского API, а затем просто добавить предложение Implements с моим новым интерфейсом. Методы, которые у меня были в интерфейсе, который соответствовал фактическому интерфейсу, тогда не нуждались бы в дополнительных деталях, и поэтому мне не пришлось бы явно их реализовывать. Однако класс был запечатан, поэтому мне пришлось вместо этого использовать прокси-вызовы для внутренней ссылки на этот класс. Результат: больше работы и много кода без веской причины.

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

Это одна из причин, по которой мне действительно нравится язык программирования Ruby ... даже основные классы открыты, не только для расширения, но и для динамического добавления и изменения функциональности, для самого класса! Это называется monkeypatching и может стать кошмаром при злоупотреблении, но с ним чертовски весело играть!

1 голос
/ 24 августа 2008

@ jjnguy

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

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

Композиция, кажется, часто упускается из виду; Слишком часто люди хотят запрыгнуть в наследство. Они не должны! Заменимость сложна. По умолчанию состав; в конечном итоге ты поблагодаришь меня.

1 голос
/ 24 августа 2008

Это может не относиться к вашему коду, но многие классы в рамках .NET специально запечатаны так, что никто не пытается создать подкласс.

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

0 голосов
/ 14 февраля 2011

В большинстве ответов (когда они обобщены) говорится, что запечатанные / завершенные классы являются инструментом для защиты других программистов от потенциальных ошибок. Существует размытая грань между осмысленной защитой и бессмысленными ограничениями. Но поскольку программист - это тот, кто должен понимать программу, я не вижу никаких причин ограничивать его от повторного использования частей класса. Большинство из вас говорят о занятиях. Но это все об объектах!

В своем первом посте DrPizza утверждает, что разработка наследуемого класса означает ожидание возможных расширений. Правильно ли я понимаю, что вы думаете, что класс должен быть наследуемым только в том случае, если он, вероятно, будет расширен хорошо? Похоже, вы были использованы для разработки программного обеспечения из самых абстрактных классов. Позвольте мне кратко объяснить, как я думаю при проектировании:

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

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

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

Запечатывание класса заставляет людей, которые могли бы воспользоваться существующим кодом БЕЗ ФАКТИЧЕСКОГО ИЗМЕНЕНИЯ ВАШЕГО ПРОДУКТА, заново изобретать колесо. (Это ключевая идея моего тезиса: наследование класса не меняет его! Это кажется довольно пешеходным и очевидным, но его обычно игнорируют).

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

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

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

0 голосов
/ 24 августа 2008

Производительность: (…) если JIT-компилятор видит вызов виртуального метода с использованием закрытых типов, JIT-компилятор может создавать более эффективный код, вызывая метод не виртуально. (…)

Это действительно хорошая причина. Таким образом, для классов, критичных к производительности, sealed и друзья имеют смысл.

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

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