Почему закрытые поля недостаточно защищены?

265

privateПолезна ли видимость полей / свойств / атрибутов класса? В ООП рано или поздно вы создадите подкласс класса, и в этом случае полезно понять и иметь возможность полностью изменить реализацию.

Одна из первых вещей, которые я делаю, когда делаю подкласс класса, - это изменение набора privateметодов на protected. Тем не менее, важно скрывать детали от внешнего мира - поэтому нам нужно, protectedа не только public.

Мой вопрос: знаете ли вы о важном случае использования, где privateвместо protectedхорошего инструмента, или двух вариантов « protected& public» будет достаточно для языков ООП?

Адам Либуша
источник
236
Для нижестоящих: Хотя я также категорически не согласен с идеями ОП, я поднимаю этот вопрос, потому что он совершенно последовательный и заслуживает ответа. Да, ОП нужно сказать, почему это неправильно, но способ сделать это - написать ответ (или предложить изменения к существующим ответам), а не понизить голосование только потому, что он еще не понял этого для себя.
Иксрек
18
Производные классы являются частью внешнего мира.
CodesInChaos
20
Не забывайте, что защищенный не всегда означает, что доступ заблокирован к иерархии наследования. В Java он также предоставляет доступ на уровне пакетов.
berry120
8
Мой профессор говорил, что «Есть вещи, которые я бы не рассказал своим детям.
Sat

Ответы:

225

Потому что, как вы говорите, по- protectedпрежнему оставляет вам возможность «полностью изменить реализацию». Он не защищает ничего в классе.

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

На практике protectedчлены - это, по сути, «открытый API для подклассов» класса, и они должны оставаться стабильными и обратно совместимыми так же, как и publicчлены. Если бы у нас не было возможности создавать настоящих privateучастников, то ничего в реализации никогда не было бы безопасно изменить, потому что вы не смогли бы исключить вероятность того, что (не злонамеренный) клиентский код каким-то образом зависел от Это.

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

Ixrec
источник
11
Я бы бросил тебе больше +1, если бы мог, так как это первый раз, privateкогда-либо на самом деле имел смысл для меня. Перспективу lib нужно использовать чаще, иначе любой, кому нравится (C-like) менталитет кодирования «абсолютный контроль, абсолютная ответственность», может пометить его как «защищающий меня от себя». Обратите внимание, что я все еще использовал privateранее, я просто всегда чувствовал хорошую документацию и соглашение об именах, вроде того, _fooчтобы указать, что вы, вероятно, не должны связываться с ним, было эквивалентно, если не лучше. Возможность детерминистически сказать «ничего не сломается» - законная, privateединственная особенность.
abluejelly
Первоначально я игнорировал случай кода публичных библиотек и фреймворков и думал более или менее только в терминах «клиентского кода». Оптимизация внутренней реализации является хорошим примером для моего вопроса, хотя я спрашиваю себя, действительно ли это происходит в реальности (особенно, когда многие люди рекомендуют, чтобы класс не превышал 1000 строк кода). Вообще, мне нравится подход Руби, где приватность - это своего рода рекомендация: «Будь драконами, будь осторожен».
Адам Либуша
9
@ AdamLibuša Несмотря на то, что если ваш код общедоступен, это гораздо сложнее, но он все равно применяется, даже если вы являетесь автором всех клиентов класса. Проблема просто меняется от невозможности некоторых рефакторингов к тому, что определенные рефакторы утомительны и подвержены ошибкам. Оптимизация - это на самом деле наименее распространенная причина для этих рефакторов в моем опыте (хотя я в основном использую Javascript), обычно это больше похоже на ошибку, которая выявляет фундаментальный недостаток реализации, который требует реструктуризации графа зависимости / вызова различных внутренних битов для достижения действительно надежное решение.
Ixrec
5
«Рано или поздно вы сделаете подкласс каждого класса», это почти наверняка не так ». И, что более важно, вы почти наверняка этого не сделаете, и вам НЕ СЛЕДУЕТ, переопределять каждую функцию в классе и изменять использование каждого элемента данных в классе. Некоторые вещи должны быть логически написаны на камне, чтобы класс имел какое-либо значение.
Jay
1
Я бы бросил тебе больше +1, если бы мог => Это то, что мы называем щедростью @abluejelly
Томас
256

В ООП рано или поздно вы создадите подкласс класса

Это не верно. Не каждый класс должен быть разделен на подклассы, и некоторые статически типизированные языки ООП даже имеют функции, предотвращающие это, например, final(Java и C ++) или sealed(C #).

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

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

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

Или, говоря словами Скотта Мейерса: на частные части класса влияет ограниченное количество кода: код самого класса.

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

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

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

Себастьян Редл
источник
34
+1 за теоретический сценарий «под влиянием бесконечного количества кода»
Broken_Window
13
Возможно, стоит также отметить, что разработчики C # фактически решили запретить переопределение унаследованного метода, если он явно не помечен как virtual, по причинам, изложенным в этом ответе.
Будет ли Vousden
11
Или, проще говоря: protectedдля методов, а не для членов данных.
Матье М.
7
Я не могу найти его сейчас, но я помню, как читал хорошо написанный ответ от @EricLippert о том, почему MS sealedбольшие части библиотеки .net и IIRC, почему он хотел бы запереть все остальное. В тот момент, когда вы позволяете сторонним наследникам начать трогать внутренние значения, вам нужно добавить огромное количество проверок / проверок работоспособности для каждого метода, потому что вы больше не можете доверять никаким инвариантам проекта относительно внутреннего состояния объектов.
Дэн Нили
4
@ ThorbjørnRavnAndersen "пока развивается, но не когда выйдет" - как вообще класс должен знать? Вы действительно хотите скомпилировать тестовую версию, которая отличается от выпущенной версии, когда вы даже не можете протестировать релизную версию? В любом случае тесты не должны иметь доступа к личным материалам.
Себастьян Редл
33

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

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

Робби Ди
источник
2
+1 за "ездить на карете и лошадях" все что угодно. Это отличная фраза, которую я хотел бы услышать больше.
Ник Хартли
2
Если кому-то нужно разделить ваш класс на подклассы, возможно, он должен иметь доступ к вашим гарантиям? И если вы не хотите, чтобы кто-то изменил ваши меры безопасности для достижения какой-то цели, возможно, не делитесь кодом? Это работает так в Ruby - приватность - это более или менее рекомендация.
Адам Либуша
3
@ AdamLibuša "Не делись кодом"? Как только вы публикуете какую-либо DLL-библиотеку, вы делитесь кодом - все структуры и методы, а также все, что доступно всему миру, особенно с языками, которые поддерживают рефлексию по умолчанию. Каждый может делать с вашим кодом все, что он хочет, - privateэто просто способ сказать «не трогайте их», и он должен применяться в контракте компилятора. В таких системах, как .NET, это также имеет важные последствия для безопасности - вы можете трогать права других только тогда, когда у вас есть полное доверие (в основном, эквивалент доступа администратора / root).
Луаан
2
@ AdamLibuša Я думаю, что ваша путаница в основном проистекает из разных подходов ООП на разных языках. Корень ООП (как он был определен изначально) - это обмен сообщениями - это означает, что все является частным, за исключением сообщений, на которые вы отвечаете. В большинстве языков OOPish это называется «держать ваши данные в секрете» - способ сделать общедоступный интерфейс как можно меньше. Единственный способ, которым пользователь (будь то подкласс или другой класс) должен манипулировать вашим классом, - через общедоступный интерфейс, который вы определили - подобно тому, как вы обычно используете руль и педали для управления автомобилем :)
Luaan
3
@ Луа +1 за то, что когда можно дотрагиваться до чужих!
Найджел Сенс
12

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

  1. Методы связаны с ними до и после условия (контракты).
  2. Объекты связывают с ними инварианты.

Модульное рассуждение об объекте происходит следующим образом (по крайней мере, в первом приближении)

  1. Докажите, что конструктор объекта устанавливает инвариант
  2. Для каждого не приватного метода предположим, что инвариант объекта и предварительное условие метода выполняются при входе, а затем докажите, что тело кода подразумевает, что постусловие и инвариант выполняются при выходе из метода.

Представьте, что мы проверяем, object Aиспользуя подход выше. А теперь хочу проверить method gиз object Bкоторых требует method fот object A. Модульные рассуждения позволяют рассуждать method gбез пересмотра реализации method f. При условии, что мы можем установить инвариант object Aи предварительное условие method fна сайте вызова, method g,мы можем принять условие post method fкак сводку поведения вызова метода. Кроме того, мы также узнаем, что после возврата вызова инвариант Aвсе еще сохраняется.

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

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

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

Охраняемые поля

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

Однако подкласс обычно обязан поддерживать объектный инвариант класса, который он расширяет. Таким образом, в языке, где protect означает только «подкласс», и где мы дисциплинированы, чтобы гарантировать, что подклассы всегда поддерживают инварианты их суперкласса, вы можете утверждать, что выбор использования защищенного вместо частного теряет только минимальную модульность ,

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

flamingpenguin
источник
8

privateпеременные в классе лучше, чем protectedпо той же причине, по которой breakоператор внутри switchблока лучше, чем goto labelоператор; что люди-программисты подвержены ошибкам

protectedПеременные поддаются непреднамеренному злоупотреблению (ошибкам программиста), так же как gotoутверждение поддается созданию кода для спагетти.

Можно ли написать рабочий код без ошибок, используя protectedпеременные класса? Да, конечно! Так же, как можно написать рабочий код без ошибок, используя goto; но, как гласит клише: «Только потому, что ты можешь, не значит, что ты должен!»

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

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

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

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

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

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

Бен Коттрелл
источник
Для downvoter - что можно изменить / улучшить в этом ответе?
Бен Коттрелл
Я не голосовал против, но сравнивая уровень доступа с перерывом / переходом?
imel96
@ imel96 Нет, сравнивая причину, по которой их избегают и обескураживают «передовой практикой» (особенно для написания нового кода). т.е. компетентный программист избегал бы publicдеталей реализации, потому что он поддается неуправляемому коду. Компетентный программист будет избегать этого, gotoпотому что он поддается недопустимому коду. Однако реальный мир таков, что иногда вы теряетесь в ужасном беспорядке унаследованного кода и не имеете другого выбора, кроме как использовать его goto, и по этой же причине у вас иногда нет другого выбора, кроме как использовать открытые / защищенные детали реализации.
Бен Коттрелл
Серьезность отличается по величине, это несопоставимо. Я мог бы жить с кодом, который имеет только publicсвойства / интерфейс, но не с кодом, который использует только goto.
imel96
2
@ imel96 Вы когда-нибудь работали в коде, который использует gotoили это только потому, что вы читали такие статьи? Я понимаю, почему goto это зло, потому что я провел 2 года, работая над древним кодом, который его использует; и я потратил еще больше времени на работу с кодом, где у «классов» есть утечка деталей реализации, publicа friendспецификаторы используются как быстрые / простые / грязные хаки. Я не принимаю аргумент, что «публичное раскрытие деталей реализации» не может вызвать переплетение спагетти на том же уровне серьезности, что и gotoпотому, что это абсолютно возможно.
Бен Коттрелл
4

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

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

Например, List<T>в .NET резервное хранилище становится приватным; если бы он был защищен, производные типы могли бы делать некоторые полезные вещи, которые иначе были бы невозможны, но будущие версии List<T>всегда должны были бы использовать свое неуклюжее монолитное резервное хранилище даже для списков, содержащих миллионы элементов. Создание частного хранилища резервных копий позволит будущим версиям List<T>использовать более эффективное хранилище резервных копий, не нарушая производные классы.

Supercat
источник
4

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

Если это предположение отклонено, то есть только два случая для рассмотрения.

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

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

  1. Автор дочернего класса пытается взломать какое-то поведение в родительском классе.

Этот случай должен быть редким (можно утверждать, что он не является законным), и не предпочтительнее, чем просто изменить исходный класс в исходной кодовой базе. Это также может быть признаком плохого дизайна. В этих случаях я бы предпочел, чтобы человек, взломавший поведение, просто использовал другие хаки, такие как друзья (C / C ++) и setAccessible(true)(Java).

Я думаю, что это безопасно отрицать это предположение.

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

аллюр
источник
2

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

  • сделать все переменные / члены данных частными . Вы не хотите, чтобы кто-то извне связывался с вашими внутренними данными. Также методы, которые предоставляют вспомогательную функциональность (например, вычисления на основе нескольких переменных-членов) для вашего открытого или защищенного интерфейса - это только для внутреннего использования, и вы можете захотеть изменить / улучшить его в будущем.
  • сделайте общий интерфейс вашего класса публичным . Это то, с чем должны работать пользователи вашего исходного класса, и как вы думаете, как должны выглядеть производные классы. Чтобы обеспечить надлежащую инкапсуляцию, это обычно только методы (и вспомогательные классы / структуры, перечисления, определения типов, все, что нужно пользователю для работы с вашими методами), а не переменные.
  • объявите защищенные методы, которые могут быть полезны для тех, кто хочет расширить / специализировать функциональные возможности вашего класса, но не должен быть частью общедоступного интерфейса - на самом деле вы обычно поднимаете приватных членов в защищенные, когда это необходимо. Если сомневаешься, пока не узнаешь
    1. ваш класс может / может / будет подкласс,
    2. и иметь четкое представление о том, какими могут быть случаи использования подклассов.

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

Мерфи
источник
1

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

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

Жюль
источник
1

Здесь много хороших ответов, но я все равно добавлю два цента. :-)

Приватность хороша по той же причине, что и глобальные данные плохо.

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

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

сойка
источник
1

Я думаю, что стоит упомянуть некоторые особые мнения.

Теоретически, хорошо контролировать уровень доступа по всем причинам, указанным в других ответах.

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

Меня также раздражает, что это означает, что их классы не закрыты для модификации.

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

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

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

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

imel96
источник
1
Если вам нужно изменить сторонний код при его использовании, либо он плохо спроектирован, либо вы не используете его повторно. Вы всегда можете повторно использовать неабстрактный класс, инкапсулируя его, но на практике вам вряд ли когда-нибудь понадобится создать его подкласс.
Маленькая Санти
0

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

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

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

J_mie6
источник
1
Я не понимаю Если игроки не расширяют классы динамически во время игры, как это можно использовать для обмана? Это будет означать, что вы импортируете ненадежный код в свою кодовую базу. Если вы говорите, что вы даете учащимся скомпилированные классы и не позволяете им обманывать в своем задании, изменяя детали реализации, установка уровня защиты не сделает это задание более безопасным. Студенты могут декомпилировать библиотеки или использовать рефлексию в своих интересах. Это, конечно, зависит от того, какой уровень принуждения вы преследуете.
Сэм
@sam Как правило, код должен быть написан с предположением, что, кто бы ни изменял его дальше, ему не нужно знать всю кодовую базу наизнанку. Это может быть даже оригинальный автор время от времени (время проходит, недостаток сна ...). Обманом могут быть ученики, которым дается скелетный код, и изменяющая логика, которой они не должны касаться.
Фил Лелло
0

Частные методы / переменные, как правило, будут скрыты от подкласса. Это может быть хорошо.

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

Защищенный метод должен проверять правильность входных данных.

Фил Лелло
источник
-1

«Закрытый» означает: не предназначен для изменения или доступа к кому-либо, кроме самого класса. Не предназначен для изменения или доступа подклассов. Подклассы? Какие подклассы? Вы не должны подкласс этого!

«защищенный» означает: предназначен только для изменения или доступа классов или подклассов. Вероятно, вычет, что вы должны подкласс, иначе почему "защищен", а не "частный"?

Здесь есть четкая разница. Если я сделаю что-то личное, ты должен держать свои грязные пальцы подальше от этого. Даже если вы подкласс.

gnasher729
источник
-1

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

Некоторые рассуждения о privateпротив protected методов :

privateметоды предотвращают повторное использование кода. Подкласс не может использовать код в закрытом методе и может нуждаться в его повторной реализации или повторной реализации метода (ов), которые изначально зависят от закрытого метода & c.

С другой стороны, любой метод, который не private может рассматриваться как API, предоставляемый классом «внешнему миру», в том смысле, что сторонние подклассы также рассматриваются как «внешний мир», как кто-то другой предложил в своем ответе. уже.

Это плохо? - Я так не думаю.

Конечно, (псевдо) публичный API блокирует первоначального программиста и препятствует рефакторингу этих интерфейсов. Но с другой стороны, почему программист не должен разрабатывать свои собственные «детали реализации» таким чистым и стабильным способом, как его общедоступный API? Должен ли он использовать privateтак, чтобы он мог быть небрежным в структурировании своего "частного" кода? Думая, может быть, он мог бы почистить это позже, потому что никто не заметит? - нет

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

Много кода (фреймворка), который я вижу, принимает непоследовательное использование private:, protectedобычно встречаются не финальные методы, которые делают только что-то большее, чем делегирование частному методу. protectedНеокончательные методы, чей контракт может быть выполнен только через прямой доступ к приватным полям.

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

Хотите расширяемость и наследование? Не делай свои методы private.

Не хотите, чтобы определенное поведение вашего класса изменилось? Сделай свои методы final.

Неужели ваш метод не может быть вызван вне определенного, четко определенного контекста? Сделайте свой метод privateи / или подумайте, как сделать требуемый четко определенный контекст доступным для повторного использования через другой protectedметод-обертку.

Вот почему я рекомендую использовать privateэкономно. И не путать privateс final. - Если реализация метода жизненно важна для общего контракта класса и, следовательно, не должна быть заменена / переопределена, сделайте это final!

Для полей privateне очень плохо. До тех пор, пока поле (я) может быть разумно «использовано» с помощью соответствующих методов (это не так getXX() или setXX()!).

JimmyB
источник
2
-1 Это не решает privateпротив protectedпервоначального вопроса, дает ошибочный ( «использовать privateэкономно»?) И идет вразрез с общим консенсусом по дизайну OO. Использование не privateимеет ничего общего с сокрытием неаккуратного кода.
Андрес Ф.
3
Вы упоминаете, что у класса есть API, но, кажется, не устанавливаете связь, которую пользователь API не хочет обременять деталями, которые ему не нужно знать. Идеальная ситуация для пользователя класса состоит в том, чтобы интерфейс содержал только те методы, которые ему нужны, и ничего больше. Создание методов privateпомогает поддерживать API в чистоте.
Бен Коттрелл
1
@ HannoBinder Я согласен, что существует множество классов, которые страдают от жесткости, однако гибкость и повторное использование кода гораздо менее важны, чем инкапсуляция и долговременное сопровождение. Рассмотрим ваш случай, когда все классы имеют защищенные члены, и производные классы могут возиться с любыми деталями реализации, которые они пожелают; как надежно выполнить модульное тестирование этих классов, зная, что что-либо в какой-либо части еще неписанного кода может привести к сбою этого кода в любое время? Сколько таких «взломов» может принять код, прежде чем он превратится в большой клубок грязи?
Бен Коттрелл
2
О, вы сказали "члены". Я говорю только о методах . Переменные-члены (поля) - это отдельная история, где конфиденциальность гораздо более оправдана.
JimmyB
2
Номенклатура в обсуждении повсеместна, потому что она не специфична для конкретного языка. «Член» в C ++ может быть данными, функцией, типом или шаблоном.
JDługosz
-1

Знаете ли вы о важном случае использования, когда закрытый вместо защищенного является хорошим инструментом, или двух вариантов «защищенный и общедоступный» будет достаточно для языков ООП?

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

Защищено : когда у вас есть что-то, что имеет специфическую для подкласса реализацию / константу.

Пример:

public abstract Class MercedesBenz() extends Car {
  //Might be useful for subclasses to know about their customers
  protected Customer customer; 

  /* Each specific model has its own horn. 
     Therefore: protected, so that each subclass might implement it as they wish
  */
  protected abstract void honk();

  /* Taken from the car class. */
  @Override
  public void getTechSupport(){
     showMercedesBenzHQContactDetails(customer);
     automaticallyNotifyLocalDealer(customer);
  }

  /* 
     This isn't specific for any subclass.
     It is also not useful to call this from inside a subclass,
     because local dealers only want to be notified when a 
     customer wants tech support. 
   */
  private void automaticallyNotifyLocalDealer(){
    ...
  }
}
Арнаб Датта
источник
2
когда у вас есть что-то, что никогда не будет полезным для какого-либо подкласса для вызова или переопределения - и это то, что вы, на мой взгляд, никогда не узнаете заранее.
Адам Либуша
Ну, мне также может понадобиться нажать кнопку сброса на моей CMOS. Но стандартное предположение заключается в том, что он мне действительно не нужен, и поэтому он помещен внутрь шасси. То же самое касается методов. Вы делаете его защищенным, если подклассу абсолютно необходимо переопределить / вызвать его (см .: сигнализация). Вы делаете это публичным, если другие стороны должны назвать это.
Арнаб Датта
-2

Мне было трудно понять этот вопрос, поэтому я хотел бы поделиться частью своего опыта:

  • Что такое защищенное поле? Ни больше ничего , чем поле, которое не может быть доступны вне класса, то есть публично , как это: $classInstance->field. И хитрость в том, что это "это оно". Дети вашего класса получат к нему полный доступ, потому что это их законная внутренняя часть.
  • Что такое личное поле? Это « настоящий приват » для вашего собственного класса и вашей собственной реализации этого класса. «Хранить в недоступном для детей месте», прямо как на бутылочке с лекарством. У вас будет гарантия, что он не будет переопределен производными вашего класса, ваши методы - при вызове - будут иметь точно то, что вы объявили

ОБНОВЛЕНИЕ: практический пример с реальной задачей, которую я решил. Вот оно: у вас есть токен, например USB или LPT (это был мой случай), и у вас есть промежуточное ПО. Токен запрашивает у вас пин-код, открывается, если он правильный, и вы можете отправить зашифрованную часть и номер ключа для расшифровки. Ключи хранятся в токене, вы не можете их прочитать, используйте только их. И были временные ключи для сеанса, подписанные ключом в токене, но хранящиеся в самом промежуточном программном обеспечении. Ключ temp не должен был просачиваться где-либо снаружи, просто существовать на уровне драйвера. И я использовал приватные поля для хранения этого временного ключа и некоторых данных, связанных с аппаратным соединением. Поэтому никакие производные не могли использовать не только общедоступный интерфейс, но и некоторые защищенные«удобные» подпрограммы, которые я сделал для задачи, но не смогли открыть сейф с ключами и HW-взаимодействием. Имеет смысл?

Алексей Веснин
источник
Извините ребята! просто испортил их - обновляю мой ответ =) СПАСИБО! Я торопился и опечатался =)
Алексей Веснин
2
Я думаю, что OP знает о различиях между двумя уровнями защиты, но интересуется, почему один должен использоваться над другим.
Сэм
@ Сэм, пожалуйста, прочитай мой ответ глубже. Это совершенно разные типы полей! только , что они имеют в общем, что они оба не могут ссылаться публично
Алексей Веснин
2
Я не уверен, что вы имеете в виду. Я знаю о различиях между частным и защищенным. Возможно, вы неправильно поняли, что я имел в виду под уровнями защиты? Я имею в виду «уровень доступа». Я пытался указать, что, хотя ваш ответ не плохой, он не имеет прямого отношения к вопросу. Похоже, ФП знает, что означают оба защищенные / частные / публичные, но не уверен, при каких обстоятельствах они захотят выбрать одно из другого. Это может быть способ, которым вы были отклонены (не я).
Сэм
@ Сэм, я думаю, я понял твою точку зрения - добавил практический пример из моей реальной практики / опыта. Это то, что вам не хватает?
Алексей Веснин