Причины использования private вместо protected для полей и методов
Это довольно простой вопрос OO, но тот, который меня беспокоит в течение некоторого времени.
Я, как правило, избегаю использования "частного" модификатора видимости для моих полей и методов в пользу protected
.
это потому, что, как правило, я не вижу никакого смысла в скрытии реализации между базовым классом и дочерним классом, за исключением случаев, когда я хочу установить конкретные рекомендации для расширения моих классов (т. е. в рамках). В большинстве случаев я думаю, что пытаюсь ограничить, как мой класс будет расширен либо мной, либо другими пользователями не выгодно.
но, для большинства людей,private
модификатор обычно является выбором по умолчанию при определении непубличного поля / метода.
Итак, можете ли вы перечислить варианты использования для private
? Есть ли основная причина всегда использовать private? Или вы также думаете, что он используется слишком часто?
7 ответов
есть некоторый консенсус, что надо предпочитайте композицию наследованию в ООП. Есть несколько причин для этого (погуглите если интересно), но главное заключается в том, что:
- наследование редко является лучшим инструментом и не так гибко, как другие решения
- защищенные члены / поля образуют интерфейс к вашим подклассам
- интерфейсы (и предположения об их будущем использовании) сложно получить право и документ правильно
поэтому, если вы решите сделать свой класс наследуемым, вы должны сделать это сознательно и со всеми плюсами и минусами.
следовательно, лучше не делать класс наследуемым и вместо этого убедиться, что он максимально гибкий (и не более), используя другие средства.
Это в основном очевидно в больших рамках, где использование вашего класса находится вне вашего контроля. Для вашего собственного маленького приложения вы этого не заметите as много, но он (наследование по умолчанию) рано или поздно укусит вас сзади, если вы не будете осторожны.
варианты
композиция означает, что вы предоставляете настраиваемость через явные (полностью абстрактные) интерфейсы (виртуальные или основанные на шаблонах).
Итак, вместо того, чтобы иметь базовый класс транспортного средства с функцией virtual drive () (наряду со всем остальным, таким как целое число для цены и т. д.), у вас будет класс транспортного средства объект интерфейса двигателя, и этот интерфейс двигателя предоставляет только функцию drive (). Теперь вы можете добавить и повторно использовать любой вид двигателя в любом месте (более или менее. :).
есть две ситуации, когда имеет значение, является ли член protected
или private
:
- если производный класс может извлечь выгоду из использования члена, то "защищенный" член позволит ему это сделать, а "частный" будет отрицать эту выгоду.
- если будущая версия базового класса может извлечь выгоду из того, что член не будет вести себя так, как в настоящей версии, создание члена "private" позволит этой будущей версии изменить поведение (или полностью исключить член), делая его "защищенным", потребует, чтобы все будущие версии класса сохраняли одно и то же поведение, тем самым лишая их выгоды, которую можно было бы извлечь из его изменения.
если можно представить реалистичный сценарий, в котором производный класс может извлечь выгоду из возможности доступа к члену, и не может представить сценарий, в котором базовый класс может извлечь выгоду из изменения своего поведения, то член должен быть protected
[предполагая, конечно,это не должно быть публичным]. Если невозможно представить сценарий, в котором производный класс получит большую выгоду от прямого доступа к члену, но можно представить сценарии, в которых будущая версия базового класса может выиграть, изменив его, тогда это должно быть private
. Эти случаи довольно ясны и просты.
если нет какого-либо правдоподобного сценария, в котором базовый класс выиграл бы от изменения члена, я бы предложил наклониться к сделать это protected
. Некоторые сказали бы, что" YAGNI " (вам это не понадобится) принцип благосклонности private
, но я не согласен. Если вы ожидаете, что другие унаследуют класс, создание частного члена не предполагает "YAGNI", а скорее "HAGNI" (ему это не понадобится). Если " вам "не нужно будет изменить поведение элемента в будущей версии класса," вам " не понадобится, чтобы это было private
. Напротив, во многих случаях вы не сможете предсказать, какие потребители вашего класса могут пригодиться. Это не значит, что нужно делать членов protected
без первая попытка определить пути можно воспользоваться меняя их, так как YAGNI
не применимо к другим решением. YAGNI применяется в случаях, когда можно будет иметь дело с будущей потребностью, если и когда она встречается, поэтому нет необходимости заниматься этим сейчас. Решение сделать членом класса, который дается другим программистам private
или protected
подразумевает решение о том, какой тип потенциала будущие потребности будут обеспечены, и это затруднит обеспечение других потребностей.
иногда оба сценария будут правдоподобными, и в этом случае может быть полезно предложить два класса-один из которых предоставляет рассматриваемые члены и класс, производный от того, что не делает (нет стандартного идиоматического для производного класса, чтобы скрыть члены, унаследованные от его родителя, хотя объявляя новые члены, которые имеют те же имена, но не компилируемые функции и помечены Ан Obsolete
атрибут будет иметь этот эффект). В качестве примера компромиссов рассмотрим List<T>
. Если тип предоставил резервный массив как защищенный элемент, можно было бы определить производный тип CompareExchangeableList<T> where T:Class
, который включал в себя вернет Interlocked.CompareExchange(_backingArray[index], newValue, oldValue)
; такой тип может использоваться любым кодом, который ожидал List<T>
, но код, который знал, что экземпляр был CompareExchangeableList<T>
можно использовать CompareExchangeItem
на нем. К сожалению, потому что List<T>
не подвергайте поддерживая массив в производные классы, невозможно определить тип, который позволяет CompareExchange
на элементах списка, но которые все равно будут использоваться кодом, ожидающим List<T>
.
тем не менее, это не означает, что разоблачение резервного массива было бы совершенно бесплатно; хотя все существующие реализации List<T>
используйте один резервный массив, Microsoft может реализовать будущие версии для использования нескольких массивов, когда список в противном случае будет расти за 84K, чтобы избежать неэффективность, связанная с кучей больших объектов. Если бы резервный массив был представлен как защищенный член, было бы невозможно реализовать такое изменение, не нарушая любой код, который полагался на этот член.
на самом деле, идеальным было бы сбалансировать эти интересы, предоставив защищенный член, который, учитывая индекс элемента списка, вернет сегмент массива, содержащий указанный элемент. Если есть только один массив, метод всегда будет возвращать ссылка на этот массив со смещением нуля, начальным индексом нуля и длиной, равной длине списка. Если будущая версия List<T>
разделить массив на несколько частей, метод может позволить производным классам эффективно обращаться к сегментам массива способами, которые были бы невозможны без такого доступа [например, используя Array.Copy
] а List<T>
может изменить способ управления резервным хранилищем без нарушения правильно написанных производных классов. Неправильно написанное производное классы могут быть нарушены при изменении базовой реализации, но это ошибка производного класса, а не базы.
Я просто предпочитаю частный, чем защищенный в случае по умолчанию, потому что я следую принципу, чтобы скрыть как можно больше, и поэтому установите видимость как можно ниже.
Я здесь. Однако я думаю, что использование защищенных переменных-членов должно быть сделано сознательно, потому что вы не только планируете наследовать, но и потому, что есть веская причина, по которой производный classed не должен использовать задатчики/геттеры свойств, определенные в базовом классе.
в ООП мы" инкапсулируем " поля-члены, чтобы мы могли осуществлять контроль над тем, как их свойства представляются и изменяются. Когда мы определяем getter / setter на нашей базе для члена переменная, мы по существу говорим, что именно так я хочу, чтобы эта переменная ссылалась/использовалась.
хотя есть исключения, обусловленные дизайном, в которых может потребоваться изменить поведение, созданное в методах геттера/сеттера базового класса, мне кажется, что это будет решение, принятое после тщательного рассмотрения альтернатив.
например, когда мне нужно получить доступ к полю члена из производного класса напрямую, а не через геттер / сеттер, Я начинаю думать, что, возможно, это конкретное свойство должно быть определено как абстрактное или даже перемещено в производный класс. Это зависит от того, насколько широка иерархия и сколько дополнительных соображений. Но для меня, шагая вокруг общественной собственности, определенной на базовом классе, начинает пахнуть.
конечно, во многих случаях "не важно", потому что мы ничего не реализуем в геттер/сеттер за доступ к переменной. Но опять же, если это так, то производный класс может получить доступ через геттер/сеттер. Это также защищает от труднодоступных ошибок позже, если используется последовательно. Если поведение геттера / сеттера для поля-члена в базовом классе каким-либо образом изменяется, а производный класс ссылается на защищенное поле напрямую, существует вероятность возникновения проблем.
вы на правильном пути. Вы делаете что-то личное, потому что ваша реализация зависит от того, что она не изменяется ни пользователем, ни потомком.
Я по умолчанию частный, а затем принять сознательное решение о том, и сколько внутренних работ я собираюсь разоблачить, вы, кажется, работать на основе, что он будет разоблачен в любом случае, так что продолжайте с ним. До тех пор, пока мы оба помним, чтобы скрестить все глаза и расставить все тройники, мы хороши.
другой вот как на это посмотреть. Если вы сделаете это частным, кто-то не сможет сделать то, что они хотят с вашей реализацией.
Если вы не сделаете это частным, кто-то может сделать то, что вы действительно не хотите, чтобы они делали с вашей реализацией.
я программировал ООП с C++ в 1993 и Java в 1995. Снова и снова я видел необходимость увеличения или пересмотра класса, обычно добавляя дополнительные функции, тесно интегрированные с классом. Способ ООП сделать это-подкласс базового класса и внести изменения в подкласс. Например, поле базового класса, первоначально упоминаемое только в другом месте базового класса, необходимо для какого-либо другого действия, или какое-либо другое действие должно изменить значение поля (или одного из полей содержащиеся члены). Если это поле является закрытым в базовом классе, подкласс не может получить к нему доступ, не может расширить функциональность. Если поле защищено, оно может это сделать.
подклассы имеют особое отношение к базовому классу, которого нет у других классов в иерархии классов: они наследуют члены базового класса. Цель наследования доступ к членам базового класса, частные банки наследования. Как разработчик базового класса должен знать, что нет подклассы когда-либо будут нуждаться в доступе к члену? В некоторых случаях это может быть ясно, но частное должно быть исключением, а не правилом. Разработчики подклассов базового класса имеют исходный код базового класса, поэтому их альтернативой является прямой пересмотр базового класса (возможно, просто изменение статуса private на protected перед подклассом). Это не чисто, хорошая практика, но это то, что рядовой заставляет вас делать.
Я новичок в ООП, но был вокруг с первых статей в ACM и IEEE. Из того, что я помню, этот стиль разработки был больше для моделирования чего-то. В реальном мире вещи, включая процессы и операции, будут иметь "частные, защищенные и публичные" элементы. Таким образом, быть верным объекту .....
внешняя сторона моделирования чего-то, Программирование больше связано с решением проблемы. Вопрос о" частных, охраняемых и публичных " элементах вызывает лишь озабоченность когда речь идет о принятии надежного решения. Как человек, решающий проблемы, я бы не совершил ошибку, если бы раскошелился на то, как другие используют мое решение для решения своих собственных проблем. Теперь имейте в виду, что основная причина для вопроса...., должен был разрешить место для проверки данных (т. е. проверка данных в допустимом диапазоне и структуре перед использованием их в вашем объекте).
имея это в виду, если ваш код решает проблему, для которой он был разработан, вы выполнили свою работу. Если другие нужно ваше решение, чтобы решить ту же или аналогичную проблему - ну, вам действительно нужно контролировать, как они это делают. Я бы сказал: "только если вы получаете какую-то выгоду от этого или знаете слабые места в своем дизайне, поэтому вам нужно защитить некоторые вещи."