Почему многие разработчики выступают против использования «защищенного» модификатора в ООП? - PullRequest
11 голосов
/ 03 сентября 2010

Мой коллега изучает Введение в объектно-ориентированное программирование , и его профессор задал вопрос для обсуждения:

Почему многие разработчикипротив использования «защищенного» модификатора на / в классах?

Когда вопрос был поднят во время обеда, мои коллеги и я не могли придумать причины, по которым кто-то мог бы быть против использования модификатора protected в классе.Оставив предпосылку вопроса в стороне (что предполагает, что многие разработчики на самом деле выступают против модификатора protected; не так ли?), Мы попытались выяснить, почему.

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

Почему разработчики могут выступать против использования модификатора protected в своем дизайне ООП?

ПРИМЕЧАНИЕ. Поскольку профессор задает общий вопрос ООП, не относящийся к какому-либо конкретному языку, я не уверен, что ответы могут быть взвешены по-разному из-за различных реализаций protected в C #, Java и т. Д..

Ответы [ 12 ]

15 голосов
/ 03 сентября 2010

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

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

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

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

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

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

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

Инкапсуляция - это хорошо, даже если она блокирует кратчайший путь к решению. ;>

5 голосов
/ 03 сентября 2010

потому что взаимодействие объектов превосходит наследование.

Джеймс Гослинг, как сообщается, сказал, что если бы он делал Java снова, он обошелся бы без классов, и пояснил, что под "классами" он подразумевал наследование .protected бессмысленно (или уменьшается до private) без наследования (хотя и не в Java), но с наследованием превращается в слизистую публичную вырожденную и тем более вJava ( Программирование Scala, глава 5 ).

Аллен Голуб пишет (в Holub on Patterns , который является книгой по Java, но великолепен с точки зрения общего ООП), что protected - это public под другим именем, особенно в Java, где. защищенный символ - это символ, которому нельзя доверять как тому, что вы видите внутри класса, в котором он объявлен. другие классы (потомки) могут поменять его у вас под носом.вы имеете дело с эффектом йо-йо.

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

4 голосов
/ 03 сентября 2010

Во-первых, protected не является гарантией конфиденциальности, если только единственный расходный класс в вашей цепочке наследования не закрыт (т.е. все базовые классы являются внутренними для сборки). Любой разработчик, который НИЧЕГО знает об ООП, может получить свой класс и показать все свои внутренние возможности миру.

Использование internal для членов обычно решает эту проблему; только ваш код, расположенный в той же сборке, которую вы разрабатываете, может видеть внутренний элемент. Общедоступные дочерние элементы, которые вы выставляете для использования другими, могут быть получены извне сборки, но внутренности, которые вы хотите скрыть, все еще скрыты. Разработчики, создающие дочерние или другие объекты в одной сборке, должны сообщать вам о том, что они используют, если ваши внутренние компоненты действительно чувствительны с точки зрения времени выполнения. Помните, что protected internal означает защищенное ИЛИ внутреннее, не защищенное И внутреннее.

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

Использование protected не страшно, оно просто не достигает того, для чего многие разработчики хотят его использовать; сделать внутреннюю часть класса доступной для «доверенных» людей, не рискуя раскрыть тонкие внутренние процессы. Это меньше контроля доступа и больше предложений доступа; используя защищенный, вы утверждаете, что этот метод полезен для детей, но сам по себе он не принесет много пользы не детям, или будет вреден для класса, если его разоблачить и неправильно использовать. Это почти то же самое, что вывесить на вашем дворе табличку с надписью «Моя дверь заперта, и я не собираюсь давать вам ключ, но у всех моих детей есть один». Это даже не половина, потому что «грабитель» может СДЕЛАТЬ одного из ваших детей и использовать их, чтобы отпереть дверь.

Распространенное и общепринятое использование protected для методов - для классов Template Method; это абстрактные классы, которые имеют общедоступный рабочий процесс скелета, но позволяют или требуют расширения определенных частей их функциональности, чтобы быть полезными. Эти части функциональности, как правило, очень сплоченные и второстепенные. Шаблон будет предоставлять общедоступную, не виртуальную «движущую функцию» и несколько дескрипторов «помощникам» как защищенные абстрактные или виртуальные методы. Это позволяет потребителям выводить класс и реализовывать помощников.

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

protected - также самый простой способ для юнит-тестирования внутренностей класса. Модульное тестирование - это хорошо, но если вы хотите написать тестовое устройство, которое использует метод, этот метод должен быть доступен извне класса. Частные просто не ходят. Внутренний обычно представляет аналогичную проблему. Чтобы вообще разрешить внутреннее тестирование, они должны быть по крайней мере защищены, чтобы вы могли получить класс ДЛЯ ЭКСПРЕСС-ЦЕЛЯ разоблачения внутренних органов. В противном случае вам понадобится какое-то действительно странное отражение, чтобы извлечь MethodInfo вашего частного метода и вызвать его (вместе с разрешениями SkipVerification CAS).

4 голосов
/ 03 сентября 2010

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

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

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

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

3 голосов
/ 03 сентября 2010

Злоупотребление protected потенциально может привести к нарушению Принципа замены Лискова ( L в SOLID) - PDF link . Принцип гласит, что классы должны быть написаны таким образом, чтобы любой подкласс мог быть заменен его базовым классом. Роберт К. Мартин подробно обсудил это в Agile Принципах, Шаблонах и Практиках на C # . Суть аргумента в том, что подтип является только подтипом, когда он заменяем для своего базового класса, а не просто имеет отношение IS-A.

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

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

3 голосов
/ 03 сентября 2010

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

1 голос
/ 03 сентября 2010

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

1 голос
/ 03 сентября 2010

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

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

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

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

1 голос
/ 03 сентября 2010

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

Иногда это действительно такчто вы хотите сделать: вы тщательно спроектировали оба интерфейса для двух разных вариантов использования.Абстрактные классы могут попасть в эту категорию: у вас есть «реализаторы», использующие интерфейс protected+public, и «пользователи», использующие только интерфейс public.Там могут быть некоторые полезные функциональные возможности, которые вы можете предоставить разработчикам, которые не нужны пользователям, и могут быть некоторые вещи конфигурации, которые пользователи не трогают, но разработчики должны настроить.Выполнение этого с защищенными методами просто избавляет вас от определения отдельного интерфейса для какой-то фабрики, чтобы вернуться к «пользователям».Так что я не думаю, что это вредно, хотя это может показаться немного странным, поскольку это не «нормальный» способ определения двух интерфейсов.

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

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

Разве этого недостаточно?Делать вещи лучше ... лучше; -)

1 голос
/ 03 сентября 2010

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

С защищенными полями / методами ваш подкласс зависит от внутренней структуры вашего родительского класса, что очень плохо для связывания. Это способ сказать: «У меня есть« закрытый »метод / поле, но мои подклассы могут использовать его», и, что еще хуже, «все нормально, что можно переопределить / изменить».

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


Ресурсы:

...