При чтении различных вопросов о переполнении стека и чужого кода общее мнение о том, как проектировать классы, закрыто. Это означает, что по умолчанию в Java и C # все закрыто, поля являются окончательными, некоторые методы являются окончательными, а иногда даже классы являются окончательными .
Идея заключается в том, чтобы скрыть детали реализации, что является очень веской причиной. Однако с существованием protected
большинства языков ООП и полиморфизма это не работает.
Каждый раз, когда я хочу добавить или изменить функциональность в классе, мне обычно мешают приватные и финальные места везде. Здесь важны детали реализации: вы берете реализацию и расширяете ее, прекрасно зная, каковы последствия. Однако, поскольку я не могу получить доступ к закрытым и конечным полям и методам, у меня есть три варианта:
- Не расширяйте класс, просто обойдите проблему, ведущую к более сложному коду
- Скопируйте и вставьте весь класс, убивая повторное использование кода
- Форк проект
Это не хорошие варианты. Почему не protected
используется в проектах, написанных на языках, которые его поддерживают? Почему некоторые проекты явно запрещают наследование от своих классов?
источник
Ответы:
Разработка классов для правильной работы при расширении, особенно когда программист, выполняющий расширение, не до конца понимает, как должен работать класс, требует значительных дополнительных усилий. Вы не можете просто взять все, что является приватным, сделать его публичным (или защищенным) и назвать это «открытым». Если вы позволяете кому-то другому изменять значение переменной, вам необходимо учитывать, как все возможные значения повлияют на класс. (Что если они установят переменную в null? Пустой массив? Отрицательное число?) То же самое применяется, когда другие люди могут вызывать метод. Это требует тщательного обдумывания.
Так что классы не должны быть открытыми, но иногда не стоит того, чтобы их открывать.
Конечно, также возможно, что авторы библиотеки просто ленились. Зависит от того, о какой библиотеке вы говорите. :-)
источник
Делать все приватным по умолчанию звучит грубо, но взгляните на это с другой стороны: когда по умолчанию всё приватно, создание чего-то публичного (или защищенного, что почти то же самое) должно быть сознательным выбором; это авторский договор класса с вами, потребителем, о том, как использовать класс. Это удобно для вас обоих: автор может свободно изменять внутреннюю работу класса, пока интерфейс остается неизменным; и вы точно знаете, на какие части класса вы можете положиться, а какие могут быть изменены.
Основная идея - «слабая связь» (также называемая «узкими интерфейсами»); его ценность заключается в том, чтобы снизить сложность Уменьшая количество способов взаимодействия компонентов, количество взаимозависимостей между ними также уменьшается; и взаимозависимость является одним из худших видов сложности, когда речь идет об обслуживании и управлении изменениями.
В хорошо спроектированных библиотеках классы, которые стоит расширить с помощью наследования, защищают и открывают членов в нужных местах и скрывают все остальное.
источник
Все, что не является частным, должно более или менее существовать с неизменным поведением в каждой будущей версии класса. Фактически, это может считаться частью API, задокументировано или нет. Следовательно, раскрытие слишком большого количества деталей, вероятно, приведет к проблемам с совместимостью позже.
Что касается "окончательного" соотв. «Запечатанные» классы, один типичный случай - неизменяемые классы. Многие части каркаса зависят от неизменяемости строк. Если бы класс String не был конечным, было бы легко (даже заманчиво) создать изменяемый подкласс String, который бы вызывал всевозможные ошибки во многих других классах при использовании вместо неизменяемого класса String.
источник
final
и / илиprivate
заблокировано и никогда не может быть изменено .public
членов;protected
члены формируют контракт только между классом, который их выставляет, и его непосредственными производными классами; напротив,public
участники для договоров со всеми потребителями от имени всех возможных настоящих и будущих унаследованных классов.В ОО есть два способа добавить функциональность в существующий код.
Первый - по наследству: вы берете класс и наследуетесь от него. Однако наследование следует использовать с осторожностью. Вы должны использовать публичное наследование, главным образом, когда у вас есть отношение isA между базой и производным классом (например, Rectangle - это Shape). Вместо этого вам следует избегать публичного наследования для повторного использования существующей реализации. По сути, публичное наследование используется для того, чтобы убедиться, что производный класс имеет тот же интерфейс, что и базовый класс (или больший), так что вы можете применять принцип подстановки Лискова .
Другой метод добавления функциональности - использование делегирования или композиции. Вы пишете свой собственный класс, который использует существующий класс как внутренний объект, и делегирует ему часть работы по реализации.
Я не уверен, в чем была идея всех тех финалов в библиотеке, которую вы пытаетесь использовать. Возможно, программисты хотели убедиться, что вы не собираетесь наследовать новый класс от их, потому что это не лучший способ расширить их код.
источник
Большинство ответов имеют право: классы должны быть запечатаны (окончательно, NotOverridable и т. Д.), Когда объект не предназначен или не предназначен для расширения.
Тем не менее, я бы не стал просто закрывать все правильное оформление кода. «O» в SOLID означает «открытый-закрытый принцип», утверждая, что класс должен быть «закрыт» для модификации, но «открыт» для расширения. Идея в том, что у вас есть код в объекте. Работает просто отлично. Добавление функциональности не должно требовать открытия этого кода и внесения хирургических изменений, которые могут нарушить ранее работающее поведение. Вместо этого нужно уметь использовать наследование или внедрение зависимостей, чтобы «расширить» класс для выполнения дополнительных действий.
Например, класс «ConsoleWriter» может получить текст и вывести его на консоль. Это делает это хорошо. Однако в определенном случае вам ТАКЖЕ нужен тот же вывод, записанный в файл. Открытие кода ConsoleWriter, изменение его внешнего интерфейса для добавления параметра «WriteToFile» к основным функциям и размещение дополнительного кода для записи файлов рядом с кодом для записи на консоли обычно считается плохой вещью.
Вместо этого вы можете сделать одно из двух: вы можете получить из ConsoleWriter форму ConsoleAndFileWriter и расширить метод Write (), чтобы сначала вызывать базовую реализацию, а затем также записывать в файл. Или вы можете извлечь интерфейс IWriter из ConsoleWriter и переопределить этот интерфейс для создания двух новых классов; FileWriter и «MultiWriter», который может быть передан другим IWriters и будет «транслировать» любой вызов, сделанный его методами, всем указанным авторам. Какой из них вы выберете, зависит от того, что вам нужно; простой вывод, ну, проще, но если у вас есть смутное предвидение того, что в конечном итоге вам понадобится отправить сообщение сетевому клиенту, три файла, два именованных канала и консоль, продолжайте и попытайтесь извлечь интерфейс и создать "Y-адаптер"; Это'
Теперь, если функция Write () никогда не объявлялась виртуальной (или была запечатана), теперь у вас проблемы, если вы не контролируете исходный код. Иногда, даже если вы делаете. Обычно это плохая позиция, и это разочаровывает пользователей API с закрытым или ограниченным исходным кодом, когда это происходит. Но есть законная причина, по которой Write () или весь класс ConsoleWriter запечатаны; в защищенном поле может храниться конфиденциальная информация (в свою очередь переопределяемая из базового класса для предоставления указанной информации). Там может быть недостаточно проверки; когда вы пишете API, вы должны предполагать, что программисты, которые потребляют ваш код, не умнее и не доброжелательнее, чем средний «конечный пользователь» конечной системы; таким образом, вы никогда не будете разочарованы.
источник
Я думаю, что это, вероятно, от всех людей, использующих оо-язык для программирования c. Я смотрю на тебя, Ява. Если ваш молоток имеет форму объекта, но вы хотите процедурную систему и / или не совсем понимаете классы, тогда ваш проект нужно будет заблокировать.
По сути, если вы собираетесь писать на оо-языке, ваш проект должен следовать оо-парадигме, которая включает расширение. Конечно, если кто-то написал библиотеку в другой парадигме, возможно, вы не должны расширять ее в любом случае.
источник
Потому что они не знают ничего лучше.
Авторы оригинала, вероятно, цепляются за неправильное понимание принципов SOLID, которые возникли в запутанном и сложном мире C ++.
Надеюсь, вы заметите, что в мире ruby, python и perl нет проблем, которые, как утверждают ответы здесь, являются причиной запечатывания. Обратите внимание, что это ортогонально к динамической типизации. Модификаторы доступа просты в работе на большинстве (всех?) Языках. Поля C ++ можно изменить, приведя к другому типу (C ++ более слабый). Java и C # могут использовать отражение. Модификаторы доступа делают вещи достаточно сложными, чтобы помешать вам делать это, если вы действительно этого не хотите.
Запечатывание классов и пометка любых членов закрытыми явно нарушает принцип, что простые вещи должны быть простыми, а сложные - возможными. Вдруг вещи, которые должны быть простыми, не так.
Я бы посоветовал вам попытаться понять точку зрения авторов оригинала. Во многом это из академической идеи инкапсуляции, которая никогда не демонстрировала абсолютного успеха в реальном мире. Я никогда не видел фреймворк или библиотеку, где какой-нибудь разработчик где-то не хотел, чтобы он работал немного по-другому, и у него не было веской причины для его изменения. Есть две возможности, которые могли помешать разработчикам оригинального программного обеспечения, которые закрыли и сделали членов закрытыми.
Я думаю, что в корпоративной структуре # 2, вероятно, так. Эти платформы C ++, Java и .NET должны быть «сделаны», и они должны следовать определенным правилам. Эти рекомендации обычно означают запечатанные типы, если тип не был явно разработан как часть иерархии типов и закрытых членов для многих вещей, которые могут быть полезны для использования другими ... но поскольку они не имеют прямого отношения к этому классу, они не раскрываются , Извлечение нового типа было бы слишком дорого для поддержки, документирования и т. Д.
Вся идея модификаторов доступа заключается в том, что программисты должны быть защищены от самих себя. «Программирование на C - это плохо, потому что оно позволяет вам выстрелить себе в ногу». Это не философия, с которой я согласен как программист.
Я очень предпочитаю подход искажения имени питона. Вы можете легко (намного легче, чем размышление) заменить рядовых, если вам нужно. Отличная рецензия доступна здесь: http://bytebaker.com/2009/03/31/python-properties-vs-java-access-modifiers/
Приватный модификатор Руби на самом деле больше похож на защищенный в C # и не имеет частного как модификатор C #. Защищенный это немного другое. Здесь есть отличное объяснение: http://www.ruby-lang.org/en/documentation/ruby-from-other-languages/
Помните, что ваш статический язык не должен соответствовать устаревшим стилям кода, написанного в прошлом.
источник
РЕДАКТИРОВАТЬ: Я считаю, что классы должны быть разработаны, чтобы быть открытым. С некоторыми ограничениями, но не следует закрывать для наследования.
Почему: «Заключительные занятия» или «закрытые занятия» кажутся мне странными. Мне никогда не нужно помечать один из моих собственных классов как «окончательный» («запечатанный»), потому что мне, возможно, придется в дальнейшем вводить эти классы.
Я купил / скачал сторонние библиотеки с классами (большинство визуальных элементов управления), и очень благодарен, что ни одна из этих библиотек не использовала «final», даже если она поддерживается языком программирования, на котором они были закодированы, потому что я закончил расширение этих классов, даже если я скомпилировал библиотеки без исходного кода.
Иногда мне приходилось иметь дело с некоторыми случайными «запечатанными» классами, и заканчивалось создание нового класса «обертки», связанного с данным классом, в котором есть похожие члены.
Классификаторы области позволяют «запечатать» некоторые функции, не ограничивая наследование.
Когда я перешел со структурированной прогр. к ООП я начал использовать закрытые члены класса, но после многих проектов я прекратил использовать защищенный и, в конце концов, объявил об этих свойствах или методах в открытом доступе , если это необходимо.
PS Мне не нравится «конечный» в качестве ключевого слова в C #, я скорее использую «запечатанный», как Java, или «стерильный», следуя метафоре наследования. Кроме того, «финал» - это слово неоднозначности, используемое в нескольких контекстах.
источник