Стоит ли когда-нибудь использовать защищенные переменные-члены?

Стоит ли когда-нибудь использовать защищенные переменные-члены? В чем преимущества и какие проблемы это может вызвать?

В PHP
В PHP
В большой кодовой базе с множеством различных компонентов классы, функции и константы могут иметь одинаковые имена. Это может привести к путанице и...
Принцип подстановки Лискова
Принцип подстановки Лискова
Принцип подстановки Лискова (LSP) - это принцип объектно-ориентированного программирования, который гласит, что объекты суперкласса должны иметь...
98
0
65 818
10
Перейти к ответу Данный вопрос помечен как решенный

Ответы 10

Ответ принят как подходящий

Should you ever use protected member variables?

Зависит от того, насколько вы придирчивы к скрытию состояния.

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

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

Можете ли вы прокомментировать производительность защищенных переменных по сравнению с частной переменной с помощью метода get / set?

Jake 24.06.2010 17:56

Я бы сказал, что это не то, о чем стоит беспокоиться, если вы не обнаружите при профилировании, что горлышко бутылки в конечном итоге является аксессуаром (чего почти никогда не бывает). Есть уловки, которые можно использовать, чтобы сделать JIT умнее, если это окажется проблемой. Например, в java вы можете намекнуть, что аксессор может быть встроен, пометив его как final. Хотя, честно говоря, производительность геттеров и сеттеров гораздо менее важна, чем работа с организацией системы и фактическими проблемами производительности, определяемыми профилировщиком.

Allain Lalonde 25.06.2010 22:07

@Jake: Никогда не принимайте проектные решения, основываясь на предположениях о производительности. Вы принимаете дизайнерские решения, основываясь на том, что вы считаете лучшим дизайном, и только если реальное профилирование показывает узкое место в вашем дизайне, вы идете и исправляете его. Обычно, если дизайн хороший, производительность тоже хорошая.

Mecki 25.11.2010 20:35
С частными членами, кроме общедоступного интерфейса, они не могут видеть детали реализации. They can just open the class and look it up, so that makes no sense?!
Black 04.03.2017 18:49

@Black Очевидно, что Allain имел в виду «они не могут доступ» этих членов и, следовательно, не могут создавать код против них, оставляя автору класса возможность удалить / изменить защищенные члены позже. (Конечно, идиома pimpl позволит скрыть их визуально, а также от единиц перевода, включая заголовок.)

underscore_d 12.05.2017 23:57

Для получения подробной информации о модификаторах доступа .Net иди сюда

У защищенных переменных-членов нет реальных преимуществ или недостатков, вопрос в том, что вам нужно в вашей конкретной ситуации. Обычно принято объявлять переменные-члены как частные и разрешать внешний доступ через свойства. Кроме того, некоторые инструменты (например, некоторые преобразователи O / R) ожидают, что данные объекта будут представлены свойствами, и не распознают общедоступные или защищенные переменные-члены. Но если вы знаете, что хотите, чтобы ваши подклассы (и ТОЛЬКО ваши подклассы) имели доступ к определенной переменной, нет причин не объявлять ее защищенной.

Желание подклассов к доступ переменной сильно отличается от желания, чтобы они могли свободно ее мутировать. Это один из основных аргументов против защищенных переменных: теперь ваш базовый класс не может предполагать, что какой-либо из его инвариантов выполняется, потому что любой производный класс может делать с защищенными членами абсолютно все, что угодно. Это главный аргумент против них. Если им просто нужно доступ данных, тогда ... напишите средство доступа. : P (Я использую защищенные переменные, хотя, вероятно, больше, чем следовало бы, и я постараюсь сократить их количество!)

underscore_d 13.05.2017 00:07

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

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

Разве no particular advantage over protected methods/properties не должен быть no particular advantage over *private* methods/properties?

Penghe Geng 25.09.2014 18:12

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

Will Dean 25.09.2014 19:19

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

Penghe Geng 26.09.2014 04:05

Еще более примечательно то, что я действительно согласен с самим собой в течение этого времени ...

Will Dean 26.09.2014 11:35

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

ncmathsadist 09.12.2014 17:22

Короче да.

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

И наоборот, частные переменные-члены также никогда не нужны; public достаточно для любого использования.

Alice 20.10.2014 06:54

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

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

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

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

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

Единственное реальное исключение из этого - если вы занимаетесь доставкой двоичных dll в форме черного ящика третьим лицам. В основном это Microsoft, поставщики «Custom DataGrid Control» и, возможно, несколько других крупных приложений, которые поставляются с библиотеками расширяемости. Если вы не относитесь к этой категории, не стоит тратить время / силы на то, чтобы беспокоиться о подобных вещах.

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

Но у меня есть один хороший пример: допустим, у вас есть какой-то универсальный контейнер. У него есть внутренняя реализация и внутренние средства доступа. Но вам нужно предложить как минимум 3 публичных доступа к своим данным: map, hash_map, vector-like. Тогда у вас будет что-то вроде:

template <typename T, typename TContainer>
class Base
{
   // etc.
   protected
   TContainer container ;
}

template <typename Key, typename T>
class DerivedMap     : public Base<T, std::map<Key, T> >      { /* etc. */ }

template <typename Key, typename T>
class DerivedHashMap : public Base<T, std::hash_map<Key, T> > { /* etc. */ }

template <typename T>
class DerivedVector  : public Base<T, std::vector<T> >        { /* etc. */ }

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

Резюме Таким образом, вы защитили данные, используемые производным классом. Тем не менее, мы должны учитывать тот факт, что базовый класс должен быть абстрактным.

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

gbjbaanb 15.11.2008 04:42

@gbjbaanb: вы противоречите себе: «это не нарушает инкапсуляцию больше, чем публичные члены» отличается от «[только] производные классы могут использовать состояние класса». «защищенный» - это что-то среднее между публичным и частным. Так что "protected [...] несколько нарушает инкапсуляцию" все еще верно ...

paercebal 30.11.2008 23:30

на самом деле, в языке C++ адаптеры контейнера, такие как std :: stack, будут открывать базовый объект контейнера с защищенной переменной с именем «c».

Johannes Schaub - litb 11.12.2008 00:57

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

Taekahn 07.07.2020 18:01

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

Вот что делает ваш суперкласс не можешь после того, как переменная-член защищена, а не закрыта с аксессорами:

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

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

  3. Установите точку останова или добавьте вывод отладки при чтении или записи переменной.

  4. Переименуйте эту переменную-член, не просматривая весь код, который может ее использовать.

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

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

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

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

Методы аксессоров / мутаторов предлагают значительно большую стабильность API и гибкость реализации при обслуживании.

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

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

Для протокола: в пункте 24 «Исключительного C++» в одной из сносок Саттер идет «вы бы никогда не написали класс, у которого есть общедоступная или защищенная переменная-член. верно? (Несмотря на плохой пример, установленный некоторыми библиотеками.)»

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

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

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

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

В качестве примера:

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

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

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