Подтвердить что ты не робот

Причины использования частного, а не защищенного для полей и методов

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

Я стараюсь избегать использования модификатора видимости 'private' для моих полей и методов в пользу protected.

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

Но для большинства людей модификатор private обычно является выбором по умолчанию при определении непубличного поля/метода.

Итак, можете ли вы указать варианты использования для private? Есть ли основная причина всегда использовать частные? Или вы также думаете, что это злоупотребление?

4b9b3361

Ответ 1

Существует некоторый консенсус относительно того, что предпочитает композицию над наследованием в ООП. Для этого есть несколько причин (google, если вам интересно), но основная часть заключается в следующем:

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

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

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

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

Альтернативы

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

Итак, вместо базового класса Vehicle с функцией виртуального диска() (наряду со всем остальным, например целого для цены и т.д.), у вас будет класс Vehicle, который использует объект интерфейса Motor и что интерфейс Motor только предоставляет функцию drive(). Теперь вы можете добавлять и повторно использовать любой вид двигателя в любом месте (более или менее.:).

Ответ 2

Существуют две ситуации, когда важно, имеет ли член protected или private:

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

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

Если нет никакого правдоподобного сценария, когда базовый класс выиграет от изменения члена, я бы предложил, чтобы он склонялся к его созданию protected. Некоторые скажут, что принцип "ЯГНИ" ( "Ты не нужен" ) предпочитает private, но я не согласен. Если вы ожидаете, что другие наследуют класс, создание частного лица не предполагает "YAGNI", а скорее "HAGNI" (он ему не понадобится). Если "вам" не понадобится изменить поведение элемента в будущей версии класса, "вам" не понадобится private. В отличие от этого, во многих случаях у вас не будет никакого способа предсказать, что может понадобиться потребителям вашего класса. Это не значит, что нужно делать члены protected, не пытаясь сначала определить способы, которыми можно было бы их изменить, поскольку YAGNI не применимо ни к одному из решений. YAGNI применяется в тех случаях, когда с будущей необходимостью будет иметь дело с будущей необходимостью, если и когда это произойдет, поэтому нет необходимости в этом разбираться. Решение сделать член класса, который предоставляется другим программистам private или protected, подразумевает решение о том, какой тип потенциальной будущей потребности будет предоставлен, и будет затруднять предоставление другого.

Иногда оба сценария будут правдоподобными, и в этом случае может быть полезно предложить два класса - один из которых предоставляет члены, о которых идет речь, и класс, полученный из того, что нет (нет стандартного идиоматического для производного класса чтобы скрыть элементы, унаследованные от его родителя, хотя объявление новых членов, которые имеют одинаковые имена, но не скомпилированные функции и помеченные атрибутом Obsolete, будет иметь такой эффект). В качестве примера рассматриваемых компромиссов рассмотрим List<T>. Если тип обнажил массив подложки в качестве защищенного элемента, можно было бы определить производный тип CompareExchangeableList<T> where T:Class, который включал бы элемент T CompareExchangeItem(index, T T newValue, T oldvalue), который возвращал бы Interlocked.CompareExchange(_backingArray[index], newValue, oldValue); такой тип может использоваться любым кодом, который ожидал a List<T>, но код, который знал, что экземпляр был CompareExchangeableList<T>, мог использовать CompareExchangeItem на нем. К сожалению, поскольку List<T> не предоставляет массив подстановки для производных классов, невозможно определить тип, который позволяет CompareExchange на элементах списка, но который по-прежнему будет использоваться кодом, ожидающим List<T>.

Тем не менее, это не означает, что разоблачение базового массива было бы совершенно без затрат; даже если все существующие реализации List<T> используют один массив поддержки, Microsoft может использовать будущие версии для использования нескольких массивов, если в противном случае список будет превышать 84K, чтобы избежать неэффективности, связанной с большой кучей объектов. Если массив поддержки был открыт как защищенный член, было бы невозможно реализовать такое изменение, не нарушая никакого кода, который полагался бы на этот член.

На самом деле идеальным было бы сбалансировать эти интересы, предоставив защищенный член, который, учитывая индекс списка, вернет сегмент массива, который содержит указанный элемент. Если имеется только один массив, метод всегда будет возвращать ссылку на этот массив со смещением нуля, начальным индексом нуля и длиной, равной длине списка. Если будущая версия List<T> разделяет массив на несколько частей, метод может позволить производным классам эффективно обращаться к сегментам массива способами, которые были бы невозможны без такого доступа [например, используя Array.Copy], но List<T> может изменить способ управления хранилищем, не разбирая правильно написанные производные классы. Неправильно написанные производные классы могут быть разбиты, если базовая реализация изменится, но это ошибка производного класса, а не базы.

Ответ 3

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

Ответ 4

Я иду сюда. Тем не менее, я думаю, что использование Protected-переменных-членов должно быть сделано убедительно, потому что вы не только планируете наследовать, но также и потому, что существует целая причина, по которой производные классы не должны использовать Property Setters/Getters, определенные в базовом классе.

В ООП мы "инкапсулируем" поля-члены, чтобы мы могли осуществлять контроль над тем, как они реализуют представление и изменяются свойства. Когда мы определяем геттер/сеттер на нашей базе для переменной-члена, мы, по сути, говорим, что ЭТО - это то, как я хочу, чтобы эта переменная ссылалась/использовалась.

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

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

Конечно, во многих случаях это "не имеет значения", потому что мы не внедряем ничего в getter/setter вне доступа к переменной. Но опять же, если это так, производный класс может так же легко получить доступ через getter/setter. Это также защищает от труднодоступных ошибок позже, если их использовать последовательно. Если поведение элемента-получателя/сеттера для поля-члена в базовом классе каким-то образом изменяется, а производный класс напрямую ссылается на защищенное поле, существует вероятность возникновения проблем.

Ответ 5

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

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

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

Если вы не сделаете его закрытым, кто-то может сделать что-то, что вам действительно не нужно делать с вашей реализацией.

Ответ 6

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

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

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

Ответ 7

Я программировал OOP с С++ в 1993 году и Java в 1995 году. Снова и снова я видел необходимость расширения или пересмотра класса, обычно добавляя дополнительные функции, тесно интегрированные с классом. Способ OOP для этого заключается в подклассе базового класса и внесении изменений в подкласс. Например, поле базового класса, первоначально упомянутое только в другом месте базового класса, необходимо для какого-либо другого действия, или какое-либо другое действие должно изменить значение поля (или одного из элементов, содержащих поле). Если это поле является приватным в базовом классе, то подкласс не может получить к нему доступ, не может расширить функциональность. Если поле защищено, оно может сделать это.

Подклассы имеют особое отношение к базовому классу, которого нет в других классах в иерархии классов: они наследуют членов базового класса. Цель наследования заключается в доступе к членам базового класса; приватное наследование. Как разработчик базового класса должен знать, что никакие подклассы никогда не будут нуждаться в доступе к члену? В некоторых случаях это может быть понятно, но частные должны быть скорее исключением, чем правилом. У базовых классов разработчиков, имеющих базовый класс, есть исходный код базового класса, поэтому их альтернатива заключается в том, чтобы напрямую пересмотреть базовый класс (возможно, просто изменив частный статус на защищенный до подкласса). Это не чистая, хорошая практика, а то, что вы делаете частным.