Почему C # не предоставляет ключевое слово «друг» в стиле C ++? [закрыто]


208

В C ++ друг ключевое слово позволяет class Aназначить в class Bкачестве своего друга. Это позволяет Class Bполучить доступ к private/ protectedчленов class A.

Я никогда не читал ничего о том, почему это осталось за пределами C # (и VB.NET). Большинство ответов на этот более ранний вопрос StackOverflow говорят о том, что это полезная часть C ++, и есть веские причины для ее использования. По своему опыту я бы согласился.

Мне кажется, что другой вопрос на самом деле спрашивает, как сделать что-то подобное friendв приложении на C #. Хотя ответы, как правило, связаны с вложенными классами, это не так элегантно, как использование friendключевого слова.

Оригинальная книга «Design Patterns» регулярно использует ее в своих примерах.

Итак, в заключение, почему friendотсутствует в C #, и каков «наилучший метод» (или способы) его моделирования в C #?

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


5
я чувствую себя защищенным внутри - это хороший компромисс ..
Гульзар Назим

3
Это не слишком запутанно, не так ли? Как я уже говорил выше, книга GOF использует ее довольно часто в примерах. Для меня это не более запутанно, чем внутреннее.
Пепел

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

2
Легко ответить: C # предлагает «внутренний» в качестве модификатора доступа, который предоставляет доступ ко всему коду в том же модуле / сборке. Это избавляет от необходимости что-то вроде друга. В Java ключевое слово «защищенный» ведет себя аналогично при доступе из того же пакета.
Sellibitze

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

Ответы:


67

Наличие друзей в программировании более или менее считается "грязным" и легко злоупотреблять. Это нарушает отношения между классами и подрывает некоторые фундаментальные атрибуты ОО-языка.

При этом это хорошая функция, и я сам много раз использовал ее в C ++; и хотел бы использовать его в C # тоже. Но я держу пари, что из-за «чистого» OOness в C # (по сравнению с псевдо-OOness в C ++) MS решила, что, поскольку у Java нет ключевого слова-друга, C # тоже не должен (просто шучу;))

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

РЕДАКТИРОВАТЬ Позвольте мне уточнить, как ключевое слово друга подрывает ООП.

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


167
C # «внутренний» на самом деле вредит инкапсуляции гораздо больше, чем C ++ «друг». С «дружбой» владелец явно дает разрешение тому, кому захочет. «Внутренний» является открытой концепцией.
Неманя Трифунович

21
Андерса следует похвалить за те функции, которые он не учел, а не за те, которые он включил.
Мехрдад Афшари

21
Я не согласен с тем, что внутренняя часть C # вредит инкапсуляции. На мой взгляд, наоборот. Вы можете использовать его для создания инкапсулированной экосистемы в сборке. Ряд объектов могут совместно использовать внутренние типы в этой экосистеме, которые не доступны остальной части приложения. Я использую «дружеские» приемы сборки для создания сборок модульных тестов для этих объектов.
Ужасно

26
Это не имеет смысла. friendне позволяет произвольным классам или функциям получать доступ к закрытым членам. Это позволяет определенным функциям или классам, которые были отмечены как друзья, делать это. Класс, владеющий закрытым членом, все еще контролирует доступ к нему на 100%. С таким же успехом можно утверждать, что публичные методы-члены. Оба гарантируют, что методы, конкретно перечисленные в классе, получают доступ к закрытым членам класса.
Джалф

8
Я думаю, что наоборот. Если в сборке имеется 50 классов, если 3 из этих классов используют какой-либо служебный класс, то сегодня единственная возможность - пометить этот служебный класс как «внутренний». При этом вы делаете его доступным не только для 3 классов, но и для остальных классов в сборке. Что, если все, что вы хотели сделать, это предоставить более детальный доступ таким образом, чтобы только три рассматриваемых класса имели доступ к служебному классу. Это на самом деле делает его более объектно-ориентированным, поскольку улучшает инкапсуляцию.
zumalifeguard

104

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

См Konrad Rudolph «s сообщение в другом потоке, или , если вы предпочитаете видеть соответствующую запись в C ++ FAQ.


... и соответствующая запись в FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
Джош Ли

28
+1 за « Использование друга - это не нарушение инкапсуляции, а, наоборот, обеспечение его соблюдения »
Наваз

2
Я думаю о семантическом мнении; и, возможно, связано с конкретной ситуацией в вопросе. Создание класса a friendдает ему доступ ко ВСЕМ вашим личным членам, даже если вам нужно только разрешить ему установить одного из них. Существуют веские аргументы в пользу того, что предоставление полей другим классам, которым они не нужны, считается «нарушением инкапсуляции». В C ++ есть места, где это необходимо (операторы перегрузки), но есть компромисс между инкапсуляцией, который необходимо тщательно рассмотреть. Кажется, дизайнеры C # чувствовали, что компромисс не стоит того.
GrandOpener

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

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

50

Для информации, еще одна связанная, но не совсем та же вещь в .NET [InternalsVisibleTo], которая позволяет сборке назначать другую сборку (например, сборку модульного теста), которая (эффективно) имеет «внутренний» доступ к типам / элементам в оригинальная сборка.


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

14

Вы должны быть в состоянии выполнить те же самые вещи, для которых «друг» используется в C ++, используя интерфейсы в C #. Это требует от вас явного определения, какие члены передаются между двумя классами, что является дополнительной работой, но также может облегчить понимание кода.

Если у кого-то есть пример разумного использования «друга», которое нельзя смоделировать с помощью интерфейсов, поделитесь им! Я хотел бы лучше понять различия между C ++ и C #.


Хорошая точка зрения. У вас была возможность взглянуть на предыдущий вопрос SO (заданный монооксидом и связанный выше?). Мне было бы интересно узнать, как лучше всего решить эту проблему в C #?
Ash

Я не уверен, как именно это сделать в C #, но я думаю, что ответ Джона Скита на вопрос о монооксиде указывает в правильном направлении. C # позволяет вложенные классы. Я на самом деле не пытался закодировать и скомпилировать решение, хотя. :)
Параппа

Я чувствую, что образец посредника - хороший пример того, где друг был бы хорош. Я реорганизую свои Формы, чтобы иметь посредника. Я хочу, чтобы моя форма могла вызывать методы типа «событие» в посреднике, но я не хочу, чтобы другие классы вызывали эти методы, подобные «событию». Функция «Друг» предоставит форме доступ к методам, не открывая ее для всего остального в сборке.
Vaccano

3
Проблема с интерфейсным подходом замены «Друг» состоит в том, что объект предоставляет интерфейс, это означает, что любой может привести объект к этому интерфейсу и получить доступ к методам! Определение области интерфейса для внутреннего, защищенного или частного не работает, как если бы это работало, вы могли бы просто охватить рассматриваемый метод! Подумайте о матери и ребенке в торговом центре. Публика - это все в мире. Внутренние это просто люди в торговом центре. Приватный это просто мать или ребенок. «Друг» - это нечто среднее между матерью и дочерью.
Марк А. Донохо

1
Вот один из них: stackoverflow.com/questions/5921612/… Поскольку я родом из C ++, отсутствие друзей сводит меня с ума почти ежедневно. За несколько лет работы с C # я обнародовал буквально сотни методов, где они могли бы быть приватными и безопасными, и все из-за отсутствия друга. Лично я думаю, что друг - это самое важное, что нужно добавить в .NET
kaalus

14

С friendC ++ разработчик имеет точный контроль над тем, кому доступны частные * члены. Но он вынужден разоблачить каждого из частных участников.

С internalC # дизайнер имеет точный контроль над набором закрытых членов, которые он выставляет. Очевидно, он может выставить только одного частного члена. Но он будет открыт для всех классов в сборке.

Как правило, разработчик желает предоставить только несколько закрытых методов выбранным нескольким другим классам. Например, в шаблоне фабрики классов может быть желательно, чтобы класс C1 создавался только фабрикой классов CF1. Поэтому класс C1 может иметь защищенный конструктор и фабрику класса друга CF1.

Как видите, у нас есть 2 измерения, вдоль которых можно нарушить инкапсуляцию. friendнарушает это по одному измерению, internalделает это по другому. Какой из них является худшим нарушением в концепции инкапсуляции? Тяжело сказать. Но было бы неплохо иметь и то friendи другое internal. Кроме того, хорошим дополнением к этим двум будет 3-й тип ключевого слова, которое будет использоваться по принципу «член за членом» (например internal) и определяет целевой класс (например friend).

* Для краткости я буду использовать «частный» вместо «частный и / или защищенный».

Ник


13

На самом деле, C # дает возможность получить то же поведение в чистом виде ООП без особых слов - это частные интерфейсы.

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

Основная идея была взята отсюда: что такое частный интерфейс?

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

Я думаю, что хорошим примером в реальной практике может служить шаблон Full State Machine, в котором один контроллер обновляет текущий объект состояния и переключается на другой объект состояния, когда это необходимо.

Ты мог:

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

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.

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()");
    }
}

И, наконец, пример того, как проверить наследование класса Controller: ControllerTest.cs

class ControllerTest: Controller
{
    public ControllerTest()
    {
        IState testObj = new PlayerIdleState();
        testObj.Update();
    }
}

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


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

@max_cn Это действительно хорошее решение, но я не вижу способа заставить его работать с фреймворками для юнит-тестирования. Какие-либо предложения?
Стив Лэнд

Я немного смущен. Если бы я хотел, чтобы внешний класс мог вызывать Update в вашем первом примере (частный интерфейс), как бы я изменил Controller, чтобы это произошло?
AnthonyMonterrosa

@Steve Land, вы можете использовать наследование, как было показано в ControllerTest.cs. Добавьте все необходимые общедоступные методы в производный класс тестирования, и вы получите доступ ко всему защищенному персоналу, который у вас есть в базовом классе.
max_cn

@AnthonyMonterrosa, мы скрываем обновление от ВСЕХ внешних ресурсов, так почему вы хотите поделиться им с кем-то;)? Конечно, вы можете добавить какой-нибудь публичный метод для доступа к закрытым членам, но он будет похож на черный ход для любых других классов. В любом случае, если вам это нужно для целей тестирования - используйте наследование, чтобы создать что-то вроде класса ControllerTest, чтобы выполнить там какие-либо тесты.
max_cn

9

Друг чрезвычайно полезен при написании юнит-теста.

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

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


Я собирался сделать комментарий о том, что «друг» полезен для заводских классов, но я рад видеть, что кто-то уже сделал это.
Эдвард Робертсон

9

В C # отсутствует ключевое слово "friend" по той же причине, что и в нем отсутствует детерминированное уничтожение. Изменение соглашений заставляет людей чувствовать себя умными, как будто их новые способы превосходят чьи-то старые способы. Это все о гордости.

Сказать, что «классы друзей плохие», так же недальновидно, как и другие неквалифицированные высказывания, такие как «не использовать gotos» или «Linux лучше Windows».

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


В C # отсутствует детерминированное уничтожение, поскольку оно медленнее, чем сборка мусора. Детерминированное уничтожение требует времени для каждого созданного объекта, в то время как сборка мусора требует времени только для текущих живых объектов. В большинстве случаев это происходит быстрее, и чем больше недолговечных объектов в системе, тем быстрее происходит сборка мусора.
Эдвард Робертсон

1
@ Эдвард Робертсон Детерминированное уничтожение не занимает времени, если не определен деструктор. Но в этом случае сборка мусора намного хуже, потому что тогда вам нужно использовать финализатор, который медленнее и недетерминирован.
каалус

1
... и память не единственный вид ресурса. Люди часто ведут себя так, как будто это правда.
cubuspl42

8

Вы можете приблизиться к C ++ "другу" с ключевым словом C # "внутренним" .


3
Близко, но не совсем там. Я предполагаю, что это зависит от того, как вы структурируете свои сборки / классы, но было бы более элегантно минимизировать количество классов, которым предоставляется доступ с использованием друга. Например, в сборке утилиты / помощника не все классы должны иметь доступ к внутренним членам.
Пепел

3
@Ash: Вам нужно привыкнуть к нему, но как только вы увидите, что сборки являются фундаментальным строительным блоком ваших приложений, они internalприобретают гораздо больше смысла и обеспечивают отличный компромисс между инкапсуляцией и полезностью. Доступ по умолчанию в Java еще лучше.
Конрад Рудольф

7

Это на самом деле не проблема с C #. Это фундаментальное ограничение в IL. C # ограничен этим, как и любой другой язык .Net, который стремится быть проверяемым. Это ограничение также включает управляемые классы, определенные в C ++ / CLI ( раздел 20.5 спецификации ).

При этом я думаю, что у Нельсона есть хорошее объяснение того, почему это плохо.


7
-1. friendне плохая вещь; напротив, это намного лучше, чем internal. И объяснение Нельсона плохое и неправильное.
Наваз

4

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

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

Ни один язык программирования не идеален. C # - один из лучших языков, которые я видел, но глупые оправдания отсутствующих функций никому не помогают. В C ++ я скучаю по простой системе событий / делегатов, рефлексии (+ автоматическая де / сериализация) и foreach, но в C # я скучаю по перегрузке операторов (да, повторю, что она вам не нужна), параметрам по умолчанию, const это не может быть обойдено, множественное наследование (да, продолжайте говорить мне, что вам это не нужно, и интерфейсы были достаточной заменой) и возможность решить удалить экземпляр из памяти (нет, это не ужасно плохо, если вы не ремесленник)


В C # вы можете перегружать большинство операторов. Вы не можете перегрузить операторы присваивания, но, с другой стороны, вы можете переопределить ||/ &&удерживая их в коротком замыкании. Параметры по умолчанию были добавлены в C # 4.
CodesInChaos

2

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

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

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


2

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

Что мы имеем? (говоря о "друге", я также говорю о "внутреннем")

  • использование "друга" делает код менее чистым относительно oO?
  • да;

  • не использует "друг" делает код лучше?

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

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

общее хорошее решение для языка программирования я вижу так:

// 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) {} ... Не уверен, что это имеет смысл; Я не знаю, как работают атрибуты или могут ли они возиться с доступностью (они делают много других «волшебных» вещей).
Грауль

2

Я отвечу только на вопрос «Как».

Здесь так много ответов, однако я хотел бы предложить своего рода «шаблон проектирования» для достижения этой функции. Я буду использовать простой языковой механизм, который включает в себя:

  • Интерфейсы
  • Вложенный класс

Например, у нас есть 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();
}

1

Я подозреваю, что это как-то связано с моделью компиляции C # - сборкой IL JIT, компилирующей это во время выполнения. то есть: по той же причине, что дженерики C # принципиально отличаются от дженериков C ++.


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

1

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


Если вы не работаете над приложением Silverlight.
каалус

1

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

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

Просто глупо заявлять, что нарушением ООП является то, что у класса протокола / «создателя» есть интимный доступ ко всем созданным классам - классу-создателю приходилось разбирать каждый бит данных по пути вверх. Что я нашел наиболее важным, так это минимизировать все дополнительные строки кода BS, к которым обычно приводит модель «ООП ради ООП». Дополнительные спагетти только делают больше ошибок.

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

Если у вас есть класс C ++, использующий ключевое слово friend, и вы хотите эмулировать его в классе C #: 1. объявите открытый класс C # 2. объявите все атрибуты / свойства / методы, которые защищены в C ++ и, таким образом, доступны для друзей как внутренние в C # 3. создать свойства только для чтения для общего доступа ко всем внутренним атрибутам и свойствам

Я согласен, что это не на 100% то же самое, что и друг, и модульное тестирование является очень ценным примером необходимости чего-то вроде друга (как, например, код журнала анализатора протокола). Однако внутренняя обеспечивает доступ к классам, которые вы хотите получить, а [InternalVisibleTo ()] обрабатывает все остальное - похоже, он был создан специально для модульного тестирования.

Что касается друга, который «лучше, потому что вы можете точно контролировать, какие классы имеют доступ» - что, черт возьми, куча подозрительных злых классов делает в одной сборке? Разбейте свои сборки!


1

Дружба может быть смоделирована путем разделения интерфейсов и реализаций. Идея такова: « Требовать конкретного экземпляра, но ограничить доступ к конструкции этого экземпляра ».

Например

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()public является общедоступным, только Friendкласс может получить к нему доступ, поскольку никто другой не может получить конкретный экземпляр Friendкласса. У него есть приватный конструктор, а фабричный New()метод возвращает интерфейс.

См. Мою статью « Друзья и члены внутреннего интерфейса бесплатно» с кодированием интерфейсов для деталей.


К сожалению, дружба не может быть смоделирована любым возможным способом. Смотрите этот вопрос: stackoverflow.com/questions/5921612/…
kaalus

1

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

У меня есть страница 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);
    }
}

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


0

Если вы работаете с C ++ и обнаруживаете, что используете ключевое слово friend самостоятельно, это очень убедительный признак того, что у вас есть проблема с дизайном, потому что, черт возьми, классу нужен доступ к закрытым членам другого класса ??


Я согласен. Мне никогда не нужно ключевое слово друга. Обычно используется для решения сложных вопросов обслуживания.
Эдуард А.

7
Перегрузка оператора, кто-нибудь ??
Мы все Моника

3
сколько раз вы находили хороший повод для серьезной перегрузки оператора?
Башмохандес

8
Я приведу вам очень простой случай, который полностью разоблачает ваш комментарий «проблема дизайна». Родитель-Ребенок. Родитель владеет своими детьми. Ребенок в коллекции «Дети» родителя принадлежит этому родителю. Установщик родительского свойства для Child не должен устанавливаться кем-либо. Его нужно назначать тогда и только тогда, когда ребенок добавляется в коллекцию детей родителя. Если это публично, любой может изменить это. Внутренний: любой в этой сборке. Частный? Ни один. Никто. Создание сеттера как друга для родителя исключает эти случаи и при этом сохраняет ваши классы отдельными, но тесно связанными. Ура !!
Марк А. Донохо,

2
Существует множество таких случаев, например, самый простой шаблон «менеджер / управляемый». Есть еще один вопрос по SO, и никто не придумал, как это сделать без друга. Не уверен, что заставляет людей думать, что один класс "всегда будет достаточно". Иногда более чем один класс должен работать вместе.
каалус

0

Bsd

Было заявлено, что друзья болят чистым OOness. С чем я согласен.

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

Я думаю, что дружба должна быть добавлена ​​к методологии ОО, но не совсем так, как в С ++. Я хотел бы иметь некоторые поля / методы, к которым может обращаться мой класс друзей, но я бы не хотел, чтобы они имели доступ ко ВСЕМ моим полям / методам. Как и в реальной жизни, я бы позволил своим друзьям получить доступ к моему личному холодильнику, но я бы не позволил им получить доступ к моему банковскому счету.

Можно реализовать это следующим образом

    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.


2
Этот параметр FriendClass не служит контролем доступа: вы можете звонить c.MyMethod((FriendClass) null, 5.5, 3)из любого класса.
Джесси МакГрю,

0

Дружба также может быть смоделирована с помощью «агентов» - некоторых внутренних классов. Рассмотрим следующий пример:

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.
  }
}

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.