Ключевое слово друга C++ позволяет class A назначать class B своим другом. Это позволяет Class B получить доступ к членам private / protectedclass A.
Я никогда не читал ничего о том, почему это было исключено из C# (и VB.NET). В большинстве ответов на этот предыдущий вопрос StackOverflow, похоже, говорится, что это полезная часть C++ и есть веские причины для ее использования. По моему опыту, я должен согласиться.
Другой вопрос, как мне кажется, действительно спрашивает, как сделать что-то похожее на friend в приложении C#. Хотя ответы обычно вращаются вокруг вложенных классов, это не кажется таким элегантным, как использование ключевого слова friend.
Исходный Книга "Паттерны дизайна" регулярно использует его в своих примерах.
Итак, вкратце, почему friend отсутствует в C#, и каков «наилучший» способ (или способы) его моделирования на C#?
(Кстати, ключевое слово internal - это нет, то же самое, оно позволяет классам все внутри всей сборки получать доступ к членам internal, в то время как friend позволяет вам передать определенный класс полный доступ другому классу ровно один)
Это не слишком запутанно, не так ли? Как я уже сказал выше, в книге GOF он довольно часто используется в примерах. Для меня это не больше сбивает с толку, чем внутреннее.
На это легко ответить: C# предлагает «внутренний» в качестве модификатора доступа, который предоставляет доступ ко всему коду в том же модуле / сборке. Это устраняет потребность в чем-то вроде друга. В Java ключевое слово «protected» ведет себя аналогично w.r.t. доступ из того же пакета.
@sellibitze, отличия от internal упоминаю в вопросе. Проблема с Interanl заключается в том, что все классы в сборке могут обращаться к внутренним членам. Это нарушает инкапсуляцию, поскольку многим из этих классов может не потребоваться доступ.
Я определенно вижу сценарии, в которых они могут быть очень полезны, как уже упоминалось о модульном тестировании. Но действительно ли вы хотите, чтобы ваши друзья получили доступ к вашим личным комнатам?
Потому что у Microsoft не было философии, что ваши друзья должны видеть ваши личные части.
Не спешите отказываться от internal. Мой опыт показывает, что дружеские отношения обычно возникают между двумя тесно связанными классами (список и узел, фабрика и виджет и т. д.), Которые удобно разделить на отдельную сборку. Возможно, это немного странно, что ваши два списка могут иметь доступ к узлам друг друга, но это не значит, что вам нужно делать эти свойства или методы доступными для внешнего / клиентского кода. Это немного странно для людей, которые привыкли писать отдельные монолитные проекты, но на самом деле разделение сборок дает много преимуществ.





Вы можете приблизиться к "другу" C++ с помощью ключевого слова C# "внутренний".
Близко, но не совсем там. Я полагаю, это зависит от того, как вы структурируете свои сборки / классы, но было бы более элегантно минимизировать количество классов, которым предоставляется доступ с помощью друга. Например, в сборке служебных / вспомогательных функций не все классы должны иметь доступ к внутренним членам.
@Ash: вам нужно привыкнуть к этому, но как только вы увидите сборки как фундаментальный строительный блок ваших приложений, internal станет гораздо более разумным и предлагает отличный компромисс между инкапсуляцией и полезностью. Однако доступ по умолчанию в Java еще лучше.
Я подозреваю, что это как-то связано с моделью компиляции C# - построением IL JIT-компиляции во время выполнения. то есть: по той же причине, по которой дженерики C# фундаментально отличаются от дженериков C++.
Я думаю, что компилятор мог бы достаточно легко поддерживать это, по крайней мере, между классами внутри сборки. Просто вопрос расслабленной проверки доступа для класса друзей в целевом классе. Другу между классами во внешних сборках, возможно, потребуются более фундаментальные изменения в CLR.
Вы должны иметь возможность выполнять те же действия, для которых "друг" используется в C++, используя интерфейсы в C#. Это требует, чтобы вы явно определяли, какие члены передаются между двумя классами, что является дополнительной работой, но также может облегчить понимание кода.
Если у кого-то есть пример разумного использования слова «друг», который нельзя смоделировать с помощью интерфейсов, поделитесь им! Я хотел бы лучше понять разницу между C++ и C#.
Хорошая точка зрения. У вас была возможность взглянуть на более ранний вопрос SO (заданный монооксидом и связанный выше?) Мне было бы интересно, как лучше всего решить это на C#?
Я не совсем уверен, как это сделать на C#, но я думаю, что ответ Джона Скита на вопрос о монооксиде указывает в правильном направлении. C# допускает вложенные классы. Однако я на самом деле не пробовал кодировать и компилировать решение. :)
Я считаю, что паттерн посредник - хороший пример того, как дружить было бы хорошо. Я реорганизую свои формы, чтобы иметь посредника. Я хочу, чтобы моя форма могла вызывать методы, подобные «событию», в посреднике, но я не хочу, чтобы другие классы вызывали эти методы, подобные «событию». Функция «Друг» предоставит форме доступ к методам, не открывая ее для всего остального в сборке.
Проблема с подходом к интерфейсу замены "Друг" заключается в том, что объект предоставляет интерфейс, это означает, что любой может преобразовать объект в этот интерфейс и иметь доступ к методам! Привязка интерфейса к внутреннему, защищенному или частному не работает, как если бы это сработало, вы можете просто указать рассматриваемый метод! Подумайте о матери и ребенке в торговом центре. Публика - это каждый в мире. Внутренний - это просто люди в торговом центре. Частным остается только мать или ребенок. «Друг» - это что-то среднее между матерью и дочерью.
Вот один из них: stackoverflow.com/questions/5921612/… Поскольку я вырос в C++, отсутствие друзей сводит меня с ума почти ежедневно. За несколько лет работы с C# я сделал буквально сотни методов общедоступными, вместо этого они могли быть приватными и более безопасными, и все из-за отсутствия друзей. Лично я считаю, что друг - это самое важное, что нужно добавить в .NET.
Интерфейсы справляются с этим хорошо. Хорошо структурированный код должен защищать от случайного неправильного использования, а не от умышленного злого умысла. Если вы строго раздаете один интерфейс извне, а пользователь читает ваш код и крадет интерфейс, задокументированный как для внутреннего использования, это не ошибка в вашем коде, это кто-то намеренно является придурком. Независимо от того, что вы делаете, кто-то всегда может использовать небезопасный код для игры с битами. Ваша работа по написанию кода заключается в том, чтобы неправильно использовать жесткий. Это невозможно, чтобы сделать что-то, что невозможно использовать неправильно.
Наличие друзей в программировании более или менее считается «грязным», и им легко злоупотреблять. Это нарушает отношения между классами и подрывает некоторые фундаментальные атрибуты объектно-ориентированного языка.
При этом это хорошая функция, и я сам много раз использовал ее на C++; и хотел бы использовать его и в C#. Но я уверен, что из-за "чистой" OOness C# (по сравнению с псевдо OOness C++) MS решила, что, поскольку у Java нет ключевого слова друга, C# тоже не должен (шутка;))
Если серьезно: внутреннее не так хорошо, как друг, но оно выполняет свою работу. Помните, что редко вы будете распространять свой код сторонним разработчикам не через DLL; так что до тех пор, пока вы и ваша команда знаете о внутренних классах и их использовании, все будет в порядке.
РЕДАКТИРОВАТЬ Позвольте мне пояснить, как ключевое слово friend подрывает ООП.
Частные и защищенные переменные и методы, пожалуй, одна из самых важных частей ООП. Идея о том, что объекты могут содержать данные или логику, которые могут использовать только они, позволяет вам писать свою реализацию функциональности независимо от вашей среды - и что ваша среда не может изменять информацию о состоянии, для обработки которой она не подходит. Используя friend, вы объединяете реализации двух классов вместе - что намного хуже, чем если бы вы просто объединили их интерфейс.
Таким образом, похоже, что причина, по которой ключевое слово «internal» находится в C#, а «friend» - нет, заключается в том, что внутреннее ключевое слово более явное, чем friend. Я думаю, как только вы видите внутренние, вы сразу понимаете, что другой класс может получить к ним доступ.
Какие фундаментальные атрибуты подорваны? Какие отношения разорваны? Я хочу понять, о чем вы говорите. Заранее спасибо!
Я не думаю, что друг был оставлен без внимания из-за "чистоты OO". Я думаю, это более вероятно, потому что C# переводится в IL при компиляции, и доступны другие типы, которые вы, возможно, захотите подружить. Точно так же обобщения C++ полностью выполняются во время компиляции, а C# - нет.
«Внутренний» C# на самом деле вредит инкапсуляции гораздо больше, чем «друг» C++. При «дружбе» хозяин прямо дает разрешение кому хочет. «Внутренний» - это открытое понятие.
Андерса следует хвалить за те особенности, которые он упустил, а не за те, которые он включил.
Вы по-прежнему можете использовать "friend", но вы можете применять его к сборкам, вместо того, чтобы позволить различным библиотекам совместно использовать внутренние классы.
Я не согласен с тем, что внутренняя часть C# мешает инкапсуляции. На мой взгляд, наоборот. Вы можете использовать его для создания инкапсулированной экосистемы в сборке. Некоторые объекты могут иметь общие внутренние типы в этой экосистеме, которые не доступны для остальной части приложения. Я использую приемы сборки «друга» для создания сборок модульных тестов для этих объектов.
В этом нет смысла. friend не позволяет произвольным классам или функциям обращаться к закрытым членам. Это позволяет определенным функциям или классам, которые были помечены как друзья, делать это. Класс, владеющий закрытым членом, по-прежнему контролирует доступ к нему на 100%. С таким же успехом можно утверждать, что методы public member. Оба гарантируют, что методы, специально перечисленные в классе, получают доступ к закрытым членам класса.
Нет, этот ответ фактически неверен, и другие темы на SO уже объясняют, почему, например. stackoverflow.com/questions/17434/…
@Konrad: ссылка, которую вы предоставили, говорит конкретно об инкапсуляции, тогда как @NelsonLaQuet обобщает ООП в отношении friend. Хотя ваша ссылка может быть правильной сама по себе, сравнение ее с этим ответом похоже на сравнение яблок и апельсинов или, по крайней мере, красных яблок и зеленых яблок.
@ Неманья Трифунович: «Внутренний» - это открытая концепция ». Не совсем. Поскольку вы, программист, контролируете, какие классы находятся в ваших сборках, вы неявно контролируете, какие классы могут получить к нему доступ. Однако в любом случае давать классам прямой доступ к вашим материалам - плохая идея, поэтому .NET представила синтаксис свойств.
Друг необходим для реализации некоторых базовых шаблонов проектирования. Как правило, это необходимо, когда несколько классов должны работать вместе, например, в менеджер / управляемый шаблон. Отсутствие друзей заставляет меня часто прибегать к хитростям, например, обнародовать личные данные.
Я думаю совсем наоборот. Если сборка имеет 50 классов, если 3 из этих классов используют какой-либо служебный класс, то сегодня ваш единственный вариант - пометить этот служебный класс как «внутренний». При этом вы не только делаете его доступным для использования 3 классам, но и остальным классам в сборке. Что, если бы все, что вы хотели сделать, - это предоставить более детализированный доступ, чтобы только три рассматриваемых класса имели доступ к служебному классу. Фактически это делает его более объектно-ориентированным, поскольку улучшает инкапсуляцию.
«Частные и защищенные переменные и методы, возможно, являются одной из наиболее важных частей ООП. Идея о том, что объекты могут содержать данные или логику, которые могут использовать только они, позволяет вам написать свою реализацию функциональности независимо от вашей среды - и что ваша среда не может изменить информацию о состоянии, для обработки которой он не подходит ". - это не аргумент, почему это подрывает ООП. Python вообще не нуждается в конфиденциальности: скорее, он зависит от принятой схемы именования, чтобы описать, к чему можно легко получить доступ, а к чему нет. Тем не менее, это во многом язык ООП.
Не уверен, насколько серьезно вы шутите о C++ "псевдо OO" и C# "чистый OO", но что наверняка, так это то, что объектно-ориентированность никоим образом не подразумевает классы, особенно функции-члены нет, и супер крайности, особенно не модификаторы доступа, такие как private или public. Классы и члены с ограниченным доступом - это лишь один из способов реализации ООП. Классическое, связанное чтение на C++, касающееся членов и нечленов: Как функции, не являющиеся членами, улучшают инкапсуляцию
«редко вы будете распространять свой код сторонним разработчикам, не используя DLL» Насколько верно это остается десять лет спустя с появлением библиотек, распространяемых как бесплатное программное обеспечение и поддерживаемых на Microsoft GitHub?
Но иногда я сталкивался с тем, что мне нужно, чтобы этот член был частным, за исключением одного класса. Я хотел бы создать класс друзей, но вместо этого я должен предоставить некоторый доступ к общедоступной области, либо сделать член общедоступным, либо, если это поле или свойство, сделать для него какой-нибудь общедоступный обработчик. Как сделать то, что должно быть частным, публичным, лучше, чем сделать это публичным только для одного класса?
Я думаю, что любому программисту на C++ полезно иметь в виду, что когда два класса становятся друзьями, они больше не являются двумя отдельными классами, они стали одним классом, реализованным из двух частей (хотя каждая часть может существовать без Другие). Это почти то же самое, что и «частичные» классы в C#.
Если вы работаете с C++ и обнаруживаете себя с помощью ключевого слова friend, это очень сильный признак того, что у вас есть проблема с дизайном, потому что какого черта классу нужен доступ к закрытым членам другого класса ??
Я согласен. Мне никогда не нужно ключевое слово друга. Обычно он используется для решения сложных проблем обслуживания.
Перегрузка оператора, кто-нибудь ??
Сколько раз вы находили хороший случай серьезной перегрузки оператора?
Я обычно перегружаю operator (), operator << и некоторые другие.
Я приведу вам очень простой случай, который полностью развенчивает ваш комментарий о «проблеме дизайна». Родитель-ребенок. Родитель владеет своими детьми. Дочерний элемент в родительской коллекции «Дети» принадлежит этому родителю. Установщик свойства Parent для Child не должен устанавливаться кем-либо. Его нужно назначать тогда и только тогда, когда дочерний элемент добавляется в родительскую коллекцию Children. tЕсли он публичный, любой может его изменить. Внутренний: любой участник этой сборки. Частный? Ни один. Никто. Сделав сеттер другом для родителя, вы избавитесь от этих случаев и по-прежнему сохраните ваши классы отдельными, но тесно связанными. Ура !!
Таких случаев очень много, например, самый базовый шаблон менеджер / управляемый. Есть еще один вопрос по SO, и никто не придумал, как это сделать без друга. Не уверен, что заставляет людей думать, что одного класса «всегда хватит». Иногда более одного класса должны работать вместе.
Ситуация, описанная «Маркелем», - прекрасный пример потребности в друге. Это будет единственный выбор, если у родителя есть список дочерних элементов, список должен быть в некотором порядке и каждый дочерний элемент должен иметь соответствующий индекс списка. Тогда очевидно, что родитель должен делать все с этим ребенком.
Для информации, еще одна связанная, но не совсем такая же вещь в .NET - это [InternalsVisibleTo], который позволяет сборке назначать другую сборку (например, сборку модульного теста), которая (по сути) имеет «внутренний» доступ к типам / членам. в оригинальной сборке.
хороший совет, так как, вероятно, часто люди, ищущие друга, хотят найти способ пройти модульное тестирование с возможностью получить больше внутренних «знаний».
На самом деле это не проблема C#. Это фундаментальное ограничение в IL. C# ограничен этим, как и любой другой язык .Net, который стремится быть проверяемым. Это ограничение также включает управляемые классы, определенные в C++ / CLI (Раздел спецификаций 20.5).
При этом я думаю, что у Нельсона есть хорошее объяснение того, почему это плохо.
-1. friend - неплохая вещь; напротив, он намного лучше, чем internal. А объяснение Нельсона плохое и неверное.
На стороне примечания. Использование друга - это не нарушение инкапсуляции, а, наоборот, обеспечение ее соблюдения. Подобно аксессорам + мутаторам, перегрузке операторов, общедоступному наследованию, понижающему преобразованию, и т.п., он часто используется неправильно, но это не означает, что ключевое слово не имеет или, что еще хуже, плохой цели.
Смотрите сообщениеКонрад Рудольф в другом потоке или, если вы предпочитаете, смотрите соответствующая запись в C++ FAQ.
... и соответствующая запись в FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
+1 за "Использование друга - это не нарушение инкапсуляции, а, наоборот, обеспечение ее соблюдения."
Думаю, дело в семантическом мнении; и, возможно, связаны с конкретной рассматриваемой ситуацией. Создание класса friend дает ему доступ ко ВСЕМ вашим частным членам, даже если вам нужно позволить ему установить только одного из них. Существует веский аргумент в пользу того, что предоставление полей другому классу, который в них не нуждается, считается «нарушением инкапсуляции». В C++ есть места, где это необходимо (операторы перегрузки), но есть компромисс инкапсуляции, который необходимо тщательно рассмотреть. Похоже, разработчики C# посчитали, что компромисс того не стоит.
Инкапсуляция - это обеспечение инвариантов. Когда мы определяем класс как друг для другого, мы явно говорим, что эти два класса сильно связаны в отношении управления инвариантами первого. Лучше, когда класс становится другом, чем выставлять все атрибуты напрямую (как общедоступное поле) или через сеттеры.
Точно. Когда вы хотите использовать друга, но не можете, вы вынуждены использовать внутренние или общедоступные, которые гораздо более открыты для того, чтобы вас тыкать в вещи, которые не должны. Особенно в командной среде.
Friend чрезвычайно полезен при написании модульного теста.
Хотя это происходит за счет небольшого загрязнения объявления вашего класса, это также является напоминанием компилятора о том, какие тесты на самом деле могут заботиться о внутреннем состоянии класса.
Я нашел очень полезную и чистую идиому, когда у меня есть фабричные классы, что делает их друзьями создаваемых ими элементов, имеющих защищенный конструктор. В частности, это было тогда, когда у меня была одна фабрика, отвечающая за создание соответствующих объектов рендеринга для объектов составителя отчетов, рендеринг в заданной среде. В этом случае у вас есть единая точка знаний о взаимосвязи между классами составителей отчетов (такими как блоки изображений, полосы макета, заголовки страниц и т. д.) И соответствующие им объекты визуализации.
Я собирался сделать комментарий о том, что "друг" может быть полезен для заводских классов, но я рад видеть, что кто-то уже сделал это.
В C# отсутствует ключевое слово "friend" по той же причине, по которой отсутствует детерминированное уничтожение. Изменение условностей заставляет людей чувствовать себя умными, как будто их новые пути лучше чужих старых. Все дело в гордости.
Сказать, что «классы друзей - это плохо», так же недальновидно, как и другие неквалифицированные утверждения, такие как «не используйте gotos» или «Linux лучше, чем Windows».
Ключевое слово "friend" в сочетании с прокси-классом - отличный способ предоставить только части определенный класса конкретному другому классу (классам). Прокси-класс может выступать в качестве надежного барьера против всех других классов. «public» не допускает такого таргетинга, а использование «protected» для получения эффекта с наследованием неудобно, если действительно нет концептуальных отношений «есть».
В C# отсутствует детерминированное разрушение, потому что он медленнее, чем сборка мусора. Детерминированное разрушение требует времени для каждого созданного объекта, тогда как сборка мусора требует времени только для текущих живых объектов. В большинстве случаев это быстрее, и чем больше недолговечных объектов в системе, тем относительно быстрее происходит сборка мусора.
@Edward Robertson Детерминированное разрушение не требует времени, если не определен деструктор. Но в этом случае сборка мусора намного хуже, потому что тогда вам нужно использовать финализатор, который работает медленнее и недетерминирован.
... и память - не единственный вид ресурса. Люди часто действуют так, как будто это правда.
вы можете оставить его приватным и использовать отражение для вызова функций. Фреймворк тестирования может сделать это, если вы попросите его протестировать частную функцию.
Если вы не работаете над приложением Silverlight.
Перестаньте оправдываться за это ограничение. друг плохой, а внутренний хороший? это одно и то же, только этот друг дает вам более точный контроль над тем, кому разрешен доступ, а кому нет.
Это для обеспечения соблюдения парадигмы инкапсуляции? так что вам нужно написать методы доступа и что теперь? как вы должны запретить всем (кроме методов класса B) вызывать эти методы? вы не можете, потому что вы тоже не можете контролировать это из-за отсутствия «друга».
Нет идеального языка программирования. C# - один из лучших языков, которые я видел, но глупые оправдания отсутствующих функций никому не помогают. В C++ мне не хватает простой системы событий / делегатов, отражения (+ автоматическая де / сериализация) и foreach, но в C# я скучаю по перегрузке операторов (да, продолжайте говорить мне, что вам это не нужно), параметрам по умолчанию, константе это невозможно обойти, множественное наследование (да, продолжайте говорить мне, что оно вам не нужно, и интерфейсы были достаточной заменой) и возможность принять решение об удалении экземпляра из памяти (нет, это не так уж плохо, если вы не являетесь ремесленник)
В C# вы можете перегрузить большинство операторов. Вы не можете перегрузить операторы присваивания, но, с другой стороны, вы можете переопределить || / &&, сохраняя при этом их короткое замыкание. Параметры по умолчанию были добавлены в C# 4.
С friend разработчик C++ имеет точный контроль над тем, кому доступны закрытые * члены. Но он вынужден разоблачить каждого из частных участников.
С internal разработчик C# имеет точный контроль над набором закрытых членов, которые он раскрывает. Очевидно, он может раскрыть только одного закрытого члена. Но он будет доступен для всех классов в сборке.
Как правило, разработчик желает предоставить только несколько частных методов избранным нескольким другим классам. Например, в шаблоне фабрики классов может быть желательно, чтобы экземпляр класса C1 создавался только фабрикой классов CF1. Следовательно, класс C1 может иметь защищенный конструктор и фабрику дружественных классов CF1.
Как видите, у нас есть 2 измерения, по которым может быть нарушена инкапсуляция. friend пробивает его по одному измерению, internal - по другому. Какой из них является более серьезным нарушением концепции инкапсуляции? Тяжело сказать. Но было бы неплохо иметь в наличии и friend, и internal. Кроме того, хорошим дополнением к этим двум будет третий тип ключевого слова, который будет использоваться по отдельности (например, internal) и указывает целевой класс (например, friend).
* Для краткости я буду использовать «частный» вместо «частный и / или защищенный».
- Ник
Раньше я регулярно использовал friend и не думаю, что это нарушение ООП или признак какой-либо ошибки в дизайне. Есть несколько мест, где это наиболее эффективное средство достижения нужного результата с наименьшим количеством кода.
Одним из конкретных примеров является создание интерфейсных сборок, которые обеспечивают коммуникационный интерфейс для некоторого другого программного обеспечения. Как правило, существует несколько тяжелых классов, которые обрабатывают сложность протокола и особенности однорангового узла и обеспечивают относительно простую модель подключения / чтения / записи / пересылки / отключения, включающую передачу сообщений и уведомлений между клиентским приложением и сборкой. Эти сообщения / уведомления необходимо обернуть в классы. Атрибуты обычно должны управляться программным обеспечением протокола, поскольку оно является их создателем, но многие вещи должны оставаться доступными только для чтения для внешнего мира.
Просто глупо заявлять, что это нарушение ООП для класса протокола / "создателя", чтобы иметь непосредственный доступ ко всем созданным классам - класс-создатель должен был изменять каждый бит данных на пути вверх. Что я считаю наиболее важным, так это свести к минимуму все лишние строки кода BS, к которым обычно приводит модель «ООП ради ООП». Дополнительные спагетти просто порождают больше ошибок.
Знают ли люди, что вы можете применить ключевое слово internal на уровне атрибута, свойства и метода? Это касается не только объявления класса верхнего уровня (хотя большинство примеров, кажется, это демонстрируют).
Если у вас есть класс C++, который использует ключевое слово friend, и вы хотите имитировать это в классе C#: 1. объявить класс C# общедоступным 2. объявить все атрибуты / свойства / методы, которые защищены в C++ и, следовательно, доступны друзьям, как внутренние в C#. 3. создать свойства только для чтения для общего доступа ко всем внутренним атрибутам и свойствам
Я согласен, что это не на 100% то же самое, что и friend, и модульный тест - очень ценный пример необходимости чего-то вроде друга (как и код журналирования анализатора протокола). Однако internal обеспечивает доступ к классам, которые вы хотите получить, а [InternalVisibleTo ()] обрабатывает все остальное - похоже, он был создан специально для модульного тестирования.
Что касается друга «быть лучше, потому что вы можете явно контролировать, какие классы имеют доступ» - что, черт возьми, группа подозрительных злых классов делает в одной и той же сборке в первую очередь? Разбиение сборок на разделы!
Начиная с .Net 3 существует InternalsVisibleToAttribute, но я подозреваю, что они добавили его только для обслуживания тестовых сборок после появления модульного тестирования. Я не вижу многих других причин для его использования.
Он работает на уровне сборки, но выполняет то же самое, что и внутренний; то есть там, где вы хотите распространить сборку, но хотите, чтобы другая нераспределенная сборка имела к ней привилегированный доступ.
Совершенно справедливо они требуют, чтобы сборка друга была сильной, чтобы никто не создал воображаемого друга вместе с вашей защищенной сборкой.
Я прочитал много умных комментариев о ключевом слове «друг», и я согласен, что это полезная вещь, но я думаю, что какое «внутреннее» ключевое слово менее полезно, и оба они все еще плохи для чистого объектно-ориентированного программирования.
Что мы имеем? (говоря о «друге» я также говорю о «внутреннем»)
да;
не использование "друга" делает код лучше?
Использование друга создает некоторые локальные проблемы, а неиспользование его создает проблемы для пользователей библиотеки кода.
общее хорошее решение для языка программирования я вижу так:
// c++ style
class Foo {
public_for Bar:
void addBar(Bar *bar) { }
public:
private:
protected:
};
// c#
class Foo {
public_for Bar void addBar(Bar bar) { }
}
Что вы думаете об этом? Я думаю, что это наиболее распространенное и чисто объектно-ориентированное решение. Вы можете открыть доступ к любому выбранному вами методу для любого класса.
Я не стражник ООП, но похоже, что он решает все претензии к другу и внутреннему. Но я бы предложил использовать атрибут, чтобы избежать расширения синтаксиса C#: [PublicFor Bar] void addBar (Bar bar) {} ... Не то чтобы я уверен, что это имеет смысл; Я не знаю, как работают атрибуты и могут ли они возиться с доступностью (они делают много других «волшебных» вещей).
Дружбу можно смоделировать, разделив интерфейсы и реализации. Идея такая: «Требовать конкретный экземпляр, но ограничить доступ для строительства этого экземпляра».
Например
interface IFriend { }
class Friend : IFriend
{
public static IFriend New() { return new Friend(); }
private Friend() { }
private void CallTheBody()
{
var body = new Body();
body.ItsMeYourFriend(this);
}
}
class Body
{
public void ItsMeYourFriend(Friend onlyAccess) { }
}
Несмотря на то, что ItsMeYourFriend() является общедоступным, только класс Friend может получить к нему доступ, поскольку никто другой не может получить конкретный экземпляр класса Friend. У него есть частный конструктор, а фабричный метод New() возвращает интерфейс.
Подробности смотрите в моей статье Друзья и участники внутреннего интерфейса бесплатно с кодированием интерфейсов.
К сожалению, дружбу невозможно смоделировать. См. Этот вопрос: stackoverflow.com/questions/5921612/…
B.s.d.
Было заявлено, что друзья вредит чистому OOness. С чем я согласен.
Также было заявлено, что инкапсуляции помогают друзья, с чем я тоже согласен.
Я думаю, что дружба должна быть добавлена в методологию объектно-ориентированного программирования, но не совсем так, как в C++. Я хотел бы иметь некоторые поля / методы, к которым мой класс друзей может получить доступ, но я НЕ хочу, чтобы они получали доступ ко ВСЕМ моим полям / методам. Как и в реальной жизни, я позволял своим друзьям иметь доступ к моему личному холодильнику, но не позволял им получать доступ к моему банковскому счету.
Это можно реализовать следующим образом
class C1
{
private void MyMethod(double x, int i)
{
// some code
}
// the friend class would be able to call myMethod
public void MyMethod(FriendClass F, double x, int i)
{
this.MyMethod(x, i);
}
//my friend class wouldn't have access to this method
private void MyVeryPrivateMethod(string s)
{
// some code
}
}
class FriendClass
{
public void SomeMethod()
{
C1 c = new C1();
c.MyMethod(this, 5.5, 3);
}
}
Это, конечно, вызовет предупреждение компилятора и повредит intellisense. Но он сделает свою работу.
Кстати, я думаю, что уверенный в себе программист должен выполнять модуль тестирования, не обращаясь к закрытым членам. это выходит за рамки возможностей, но попробуйте почитать про TDD. однако, если вы все еще хотите это сделать (имея друзей на C++), попробуйте что-нибудь вроде
#if UNIT_TESTING
public
#else
private
#endif
double x;
поэтому вы пишете весь свой код без определения UNIT_TESTING, и когда вы хотите выполнить модульное тестирование, вы добавляете #define UNIT_TESTING в первую строку файла (и пишете весь код, который выполняет модульное тестирование, под #if UNIT_TESTING). С этим нужно обращаться осторожно.
Поскольку я считаю, что модульное тестирование - плохой пример для использования друзьями, я бы привел пример, почему я считаю, что друзья могут быть хорошими. Предположим, у вас есть система (класс) взлома. По мере использования тормозная система изнашивается и требует ремонта. Теперь вы хотите, чтобы это исправил только лицензированный механик. Чтобы сделать пример менее тривиальным, я бы сказал, что механик будет использовать свою личную (частную) отвертку, чтобы исправить это. Вот почему класс механики должен дружить с классом breakSystem.
Этот параметр FriendClass не служит для контроля доступа: вы можете вызывать c.MyMethod((FriendClass) null, 5.5, 3) из любого класса.
Некоторые предполагают, что с помощью друга все может выйти из-под контроля. Я бы согласился, но это не умаляет его полезности. Я не уверен, что друг обязательно вредит парадигме объектно-ориентированного программирования не больше, чем публикация всех членов вашего класса. Конечно, язык позволит вам сделать всех ваших участников общедоступными, но это дисциплинированный программист, который избегает такого типа шаблона проектирования. Точно так же дисциплинированный программист прибегнет к использованию друга для конкретных случаев, когда это имеет смысл. Я чувствую, что в некоторых случаях внутренняя экспозиция слишком велика. Зачем открывать класс или метод для всего в сборке?
У меня есть страница ASP.NET, которая наследует мою собственную базовую страницу, которая, в свою очередь, наследует System.Web.UI.Page. На этой странице у меня есть код, который обрабатывает отчеты об ошибках конечного пользователя для приложения в защищенном методе.
ReportError("Uh Oh!");
Теперь у меня есть пользовательский элемент управления, который содержится на странице. Я хочу, чтобы пользовательский элемент управления мог вызывать методы сообщения об ошибках на странице.
MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");
Этого не может быть, если метод ReportError защищен. Я могу сделать его внутренним, но он доступен для любого кода сборки. Я просто хочу, чтобы он был доступен для элементов пользовательского интерфейса, которые являются частью текущей страницы (включая дочерние элементы управления). В частности, я хочу, чтобы мой базовый класс управления определял точно такие же методы сообщения об ошибках и просто вызывал методы на базовой странице.
protected void ReportError(string str) {
MyBasePage bp = Page as MyBasePage;
bp.ReportError(str);
}
Я считаю, что что-то вроде друга может быть полезно и реализовано на языке, не делая язык менее «объектно-ориентированным», например, в виде атрибутов, так что у вас могут быть классы или методы, дружащие с определенными классами или методами, что позволяет разработчику предоставлять очень специфический доступ. Возможно что-то вроде ... (псевдокод)
[Friend(B)]
class A {
AMethod() { }
[Friend(C)]
ACMethod() { }
}
class B {
BMethod() { A.AMethod() }
}
class C {
CMethod() { A.ACMethod() }
}
В случае с моим предыдущим примером, возможно, есть что-то вроде следующего (можно поспорить с семантикой, но я просто пытаюсь донести идею):
class BasePage {
[Friend(BaseControl.ReportError(string)]
protected void ReportError(string str) { }
}
class BaseControl {
protected void ReportError(string str) {
MyBasePage bp = Page as MyBasePage;
bp.ReportError(str);
}
}
На мой взгляд, концепция друга не более рискованна, чем публикация вещей или создание общедоступных методов или свойств для доступа к членам. Во всяком случае, друг допускает другой уровень детализации в доступности данных и позволяет вам сузить эту доступность, а не расширять ее за счет внутренних или общедоступных.
Дружбу также можно смоделировать с помощью «агентов» - некоторых внутренних классов. Рассмотрим следующий пример:
public class A // Class that contains private members
{
private class Accessor : B.BAgent // Implement accessor part of agent.
{
private A instance; // A instance for access to non-static members.
static Accessor()
{ // Init static accessors.
B.BAgent.ABuilder = Builder;
B.BAgent.PrivateStaticAccessor = StaticAccessor;
}
// Init non-static accessors.
internal override void PrivateMethodAccessor() { instance.SomePrivateMethod(); }
// Agent constructor for non-static members.
internal Accessor(A instance) { this.instance = instance; }
private static A Builder() { return new A(); }
private static void StaticAccessor() { A.PrivateStatic(); }
}
public A(B friend) { B.Friendship(new A.Accessor(this)); }
private A() { } // Private constructor that should be accessed only from B.
private void SomePrivateMethod() { } // Private method that should be accessible from B.
private static void PrivateStatic() { } // ... and static private method.
}
public class B
{
// Agent for accessing A.
internal abstract class BAgent
{
internal static Func<A> ABuilder; // Static members should be accessed only by delegates.
internal static Action PrivateStaticAccessor;
internal abstract void PrivateMethodAccessor(); // Non-static members may be accessed by delegates or by overrideable members.
}
internal static void Friendship(BAgent agent)
{
var a = BAgent.ABuilder(); // Access private constructor.
BAgent.PrivateStaticAccessor(); // Access private static method.
agent.PrivateMethodAccessor(); // Access private non-static member.
}
}
Было бы намного проще, если бы он использовался для доступа только к статическим членам. Преимущества такой реализации заключаются в том, что все типы объявляются во внутренней области дружественных классов и, в отличие от интерфейсов, позволяет получить доступ к статическим членам.
Отвечу только на вопрос "Как".
Здесь так много ответов, однако я хотел бы предложить своего рода «шаблон проектирования» для достижения этой функции. Я буду использовать простой языковой механизм, который включает:
Например, у нас есть 2 основных класса: Студент и Университет. У студента есть средний балл, доступ к которому имеет только университет. Вот код:
public interface IStudentFriend
{
Student Stu { get; set; }
double GetGPS();
}
public class Student
{
// this is private member that I expose to friend only
double GPS { get; set; }
public string Name { get; set; }
PrivateData privateData;
public Student(string name, double gps) => (GPS, Name, privateData) = (gps, name, new PrivateData(this);
// No one can instantiate this class, but Student
// Calling it is possible via the IStudentFriend interface
class PrivateData : IStudentFriend
{
public Student Stu { get; set; }
public PrivateData(Student stu) => Stu = stu;
public double GetGPS() => Stu.GPS;
}
// This is how I "mark" who is Students "friend"
public void RegisterFriend(University friend) => friend.Register(privateData);
}
public class University
{
var studentsFriends = new List<IStudentFriend>();
public void Register(IStudentFriend friendMethod) => studentsFriends.Add(friendMethod);
public void PrintAllStudentsGPS()
{
foreach (var stu in studentsFriends)
Console.WriteLine($"{stu.Stu.Name}: stu.GetGPS()");
}
}
public static void Main(string[] args)
{
var Technion = new University();
var Alex = new Student("Alex", 98);
var Jo = new Student("Jo", 91);
Alex.RegisterFriend(Technion);
Jo.RegisterFriend(Technion);
Technion.PrintAllStudentsGPS();
Console.ReadLine();
}
Фактически, C# дает возможность получить такое же поведение в чистом виде ООП без специальных слов - это частные интерфейсы.
Поскольку вопрос Что представляет собой эквивалент слова friend в C#? был помечен как дублирующий к этой статье, и там никто не предлагает действительно хорошей реализации - я покажу здесь ответы на оба вопроса.
Основная идея была взята отсюда: Что такое приватный интерфейс?
Скажем, нам нужен какой-то класс, который мог бы управлять экземплярами других классов и вызывать для них какие-то специальные методы. Мы не хотим давать возможность вызывать эти методы другим классам. Это в точности то же самое, что ключевое слово друга C++ делает в мире C++.
Я думаю, что хорошим примером на практике может быть шаблон Full State Machine, когда какой-либо контроллер обновляет объект текущего состояния и при необходимости переключается на другой объект состояния.
Ты мог бы:
Controller.cs
public class Controller
{
private interface IState
{
void Update();
}
public class StateBase : IState
{
void IState.Update() { }
}
public Controller()
{
//it's only way call Update is to cast obj to IState
IState obj = new StateBase();
obj.Update();
}
}
Program.cs
class Program
{
static void Main(string[] args)
{
//it's impossible to write Controller.IState p = new Controller.StateBase();
//Controller.IState is hidden
var p = new Controller.StateBase();
//p.Update(); //is not accessible
}
}
Ну что насчет наследования?
Нам нужно использовать технику, описанную в Поскольку явные реализации членов интерфейса не могут быть объявлены виртуальными, и пометить IState как защищенный, чтобы также можно было наследовать от контроллера.
Controller.cs
public class Controller
{
protected interface IState
{
void Update();
}
public class StateBase : IState
{
void IState.Update() { OnUpdate(); }
protected virtual void OnUpdate()
{
Console.WriteLine("StateBase.OnUpdate()");
}
}
public Controller()
{
IState obj = new PlayerIdleState();
obj.Update();
}
}
PlayerIdleState.cs
public class PlayerIdleState: Controller.StateBase
{
protected override void OnUpdate()
{
base.OnUpdate();
Console.WriteLine("PlayerIdleState.OnUpdate()");
}
}
И, наконец, пример того, как проверить наследование броска контроллера класса: ControllerTest.cs
class ControllerTest: Controller
{
public ControllerTest()
{
IState testObj = new PlayerIdleState();
testObj.Update();
}
}
Надеюсь, я рассмотрю все случаи и мой ответ был полезен.
Это отличный ответ, который требует больше голосов.
@max_cn Это действительно хорошее решение, но я не вижу способа заставить его работать с макетными фреймворками для модульного тестирования. Какие-либо предложения?
Я немного запутался. Если бы я хотел, чтобы внешний класс мог вызывать Update в вашем первом примере (частный интерфейс), как бы мне изменить Controller, чтобы это произошло?
@Steve Land, вы можете использовать наследование, как было показано в ControllerTest.cs. Добавьте все необходимые общедоступные методы в производный тестовый класс, и вы получите доступ ко всему защищенному персоналу, имеющемуся в базовом классе.
@AnthonyMonterrosa, мы скрываем обновление от ВСЕХ внешних ресурсов, так почему вы хотите поделиться им с кем-то;)? Конечно, вы можете добавить какой-нибудь общедоступный метод для доступа к частным членам, но это будет как бэкдор для любых других классов. В любом случае, если вам это нужно для целей тестирования - используйте наследование, чтобы создать что-то вроде класса ControllerTest, чтобы делать там какие-либо тестовые случаи.
Это чрезвычайно многословный и запутанный способ добиться худшего эффекта по сравнению с тем, что предоставляет "друг" (можно по-прежнему приводить к интерфейсу и вызывать любые функции, которые захотят). Отсутствие друга в C# кусает меня почти каждый день в моей повседневной работе. Аргументы «чистого ООП» просто неверны: иногда два класса должны иметь частное разделяемое состояние. Друг обеспечивает это. «Чистое ООП» - нет.
я чувствую себя защищенным изнутри - хороший компромисс.