Должны ли защищенные атрибуты всегда быть запрещены?

StackOverflow https://stackoverflow.com/questions/76194

  •  09-06-2019
  •  | 
  •  

Вопрос

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

Используете ли вы защищенные атрибуты ?для чего вы их используете ?

Это было полезно?

Решение

В этом интервью о дизайне Билла Веннерса, Джошуа Блоха, автора Эффективная Java говорит:

Доверяющие Подклассы

Билл Веннерс: Я должен верить, что подклассы более тесно, чем не подклассы?Например, делаю ли я так, чтобы реализации подкласса было проще сломать меня, чем я сделал бы это для не-подкласса? В частности, как вы относитесь к защищенным данным?

Джош Блох: Написать что-то одновременно поддающееся подклассу и надежное против вредоносного подкласса на самом деле довольно сложно, предполагая, что вы предоставляете подклассу доступ к вашим внутренним структурам данных.Если подкласс не имеет доступа к чему-либо, чего нет у обычного пользователя , то подклассу сложнее наносить ущерб.Но если вы не сделаете все свои методы окончательными, подкласс все равно может нарушить ваши контракты, просто выполнив неправильные действия в ответ на вызов метода .Именно поэтому критически важные для безопасности классы, такие как String , являются окончательными.В противном случае кто-нибудь мог бы написать подкласс, который делает строки изменяемыми, что было бы достаточным для нарушения безопасности.Таким образом, вы должны доверять своим подклассам.Если вы им не доверяете, то вы не можете разрешить их, потому что подклассы могут очень легко заставить класс нарушать свои контракты.

Что касается защищенных данных в целом, это необходимое зло.Это должно быть сведено к минимуму.Наиболее защищенные данные и защищенные методы сводятся к обязательству по реализации детализации.Защищенное поле - это деталь реализации, которую вы делаете видимой для подклассов.Даже защищенный метод является частью внутренней структуры, которую вы делаете видимой для подклассов.

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

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

В качестве примера, если вы посмотрите на AbstractList, вы обнаружите, что существует защищенный метод для удаления диапазона списка одним выстрелом (removeRange).Почему это там?Потому что обычная идиома для удаления диапазона, основанная на общедоступном API, заключается в вызове subList чтобы заполучить подлодку-List, а затем вызовите clear на что суб-List.Однако без этого конкретного защищенного метода единственное , что clear можно было бы сделать это неоднократно удалять отдельные элементы.

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

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

Билл Веннерс: А защищенные данные?

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

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

Другие советы

C#:

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

Они могут понадобиться вам для статического (или "глобального") атрибута, которым вы хотите воспользоваться для своих подклассов или классов из того же пакета (если речь идет о java).

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

Скотт Мейерс говорит не надо используйте защищенные атрибуты в эффективном C ++ (3-е изд.):

Пункт 22 повестки дня:Объявляйте элементы данных закрытыми.

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

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

Тот Самый protected ключевое слово - это концептуальная ошибка и ошибка языкового дизайна, а также нескольких современных языков, таких как Ним и цейлонский (см. http://ceylon-lang.org/documentation/faq/language-design/#no_protected_modifier), которые были тщательно разработаны, а не просто копировали распространенные ошибки, такого ключевого слова нет.

Инкапсуляцию нарушают не защищенные члены, а те, которые не должны подвергаться воздействию, что нарушает инкапсуляцию...не имеет значения, являются ли они защищенными или общедоступными.Проблема с protected заключается в том , что это ошибочно и вводит в заблуждение ...объявляющие участники protected (вместо того, чтобы private) не защищает их, а делает обратное, точно так же, как public делает.Защищенный элемент, будучи доступным за пределами класса, открыт для всего мира, и поэтому его семантика должна сохраняться вечно, точно так же, как в случае с public.Вся идея "защищенного" - это чепуха ...инкапсуляция - это не безопасность, и ключевое слово только усиливает путаницу между ними.Вы можете немного помочь, избегая любого использования protected в ваших собственных классах - если что-то является внутренней частью реализации, не является частью семантики класса и может измениться в будущем, то сделайте это частным или внутренним для вашего пакета, модуля, сборки и т.д.Если это неизменяемая часть семантики класса, то сделайте ее общедоступной, и тогда вы не будете раздражать пользователей вашего класса, которые могут видеть, что в документации есть полезный элемент, но не могут его использовать, если только они не создают свои собственные экземпляры и не могут добраться до него с помощью подкласса.

Я не использую защищенные атрибуты в Java, потому что там они защищены только пакетом.Но в C ++ я буду использовать их в абстрактных классах, позволяя наследующему классу наследовать их напрямую.

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

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

Недавно я работал над проектом, где "защищенный" участник был очень хорошей идеей.Классовая иерархия была чем - то вроде:

[+] Base
 |
 +--[+] BaseMap
 |   |
 |   +--[+] Map
 |   |
 |   +--[+] HashMap
 |
 +--[+] // something else ?

В базе реализован std::list, но ничего больше.Прямой доступ к списку был запрещен пользователю, но поскольку базовый класс был неполным, он в любом случае полагался на производные классы для реализации косвенного обращения к списку.

Косвенное указание может исходить по крайней мере из двух вариантов:std::map и stdext::hash_map.Обе карты будут вести себя одинаково, но для того, чтобы hash_map был хешируемым, ключ должен быть хешируемым (в VC2003, приводимым к size_t).

Таким образом, BaseMap реализовала TMap как шаблонный тип, который представлял собой контейнер, подобный карте.

Map и HashMap были двумя производными классами базовой карты, один из которых специализировался на базовой карте std::map, а другой - на stdext::hash_map.

Итак:

  • База не была пригодна для использования как таковая (никаких общедоступных средств доступа !) и предоставляла только общие функции и код

  • Базовая карта нуждалась в легком чтении / записи в std::list

  • Map и HashMap нуждались в легком доступе для чтения / записи к TMap, определенному в BaseMap.

Для меня единственным решением было использовать protected для std::list и переменных-членов TMap.Я бы ни за что не поставил эти "приватные", потому что в любом случае я бы раскрыл все или почти все их функции с помощью средств доступа для чтения / записи.

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

Но в остальном следует избегать защиты, насколько это возможно (т. е.:Используйте private по умолчанию и public, когда вы должны предоставить доступ к методу).

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

Подумайте об этом:у вас есть класс, который является вашим базовым классом.У него есть довольно много атрибутов, которые вы не хотите использовать в дочерних объектах.Вы могли бы написать функцию get / set для каждого из них, или вы можете просто установить их.

Мой типичный пример - обработчик файлов / потоков.Вы хотите получить доступ к обработчику (т. е.файловый дескриптор), но вы хотите скрыть его от других классов.Это намного проще, чем написать для него функцию set / get.

В общем, да.Защищенный метод обычно лучше.

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

В последнее время я начал отделять то, что вы делаете с примитивами и необработанными коллекциями, от того, что вы делаете с хорошо сформированными классами.Примитивы и коллекции ВСЕГДА должны быть частными.

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

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

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

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

(C#)

static Random rnd=new Random();
//...
if (rnd.Next()%1000==0) throw new Exception("My base class sucks! HAHAHAHA! xD");
//...

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

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

Но лично мне не нравится придерживаться принципов ооп любой ценой.Особенно создание свойств с единственной целью - сделать элементы данных закрытыми.

(C#):

private _foo;
public foo
{
   get {return _foo;}
   set {_foo=value;}
}

Это было мое личное мнение.

Но делайте то, что требует ваш босс (если ему нужны личные поля, то делайте это).

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

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

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

Инкапсуляция - это здорово, но она может зайти слишком далеко.Я видел классы, чьи собственные частные методы обращались к своим переменным-членам, используя только методы получения / установки.Это излишество, поскольку, если класс не может доверять своим собственным частным методам со своими собственными личными данными, кому он может доверять?

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top