Чому C # не пропонує ключове слово "друг" у стилі C ++? [зачинено]


208

У C ++ один ключове слово дозволяє class Aпризначити в class Bякості свого друга. Це дозволяє Class Bотримати доступ до private/ protectedчленів class A.

Я ніколи нічого не читав, чому це залишилось поза C # (і VB.NET). Більшість відповідей на це попереднє питання StackOverflow, здається, говорить про те, що це корисна частина C ++ і є вагомі причини для її використання. На моєму досвіді я мав би погодитися.

Інше питання , як мені здається, справді запитує, як зробити щось подібне friendв додатку C #. Хоча відповіді, як правило, обертаються навколо вкладених класів, це виглядає не так елегантно, як використання friendключового слова.

Оригінальна книга «Шаблони дизайну» використовує її регулярно на своїх прикладах.

Отже, підсумовуючи, чому цього не friendвистачає на C # і який спосіб (або способи) "найкращої практики" моделювання його в C #?

(До речі, internalключове слово - це не одне і те ж, воно дозволяє всім класам у всій збірці отримати доступ до internalчленів, в той час як friendдозволяє надати певному класу повний доступ саме до одного іншого класу)


5
Я відчуваю, що внутрішній захищений - це гарний компроміс ..
Гульзар Назим

3
Це не надто заплутано? Як я вже говорив вище, книга GOF використовує її досить часто на прикладах. Для мене це не більше заплутано, ніж внутрішнє.
Еш

7
Я, безумовно, бачу сценарії, де вони можуть бути дуже корисними, як уже згадувалося Unit Testing. Але ви дійсно хочете, щоб ваші друзі отримували доступ до ваших приватних осіб?
Дансвайн

2
Відповісти легко: C # пропонує "внутрішній" як модифікатор доступу, який надає доступ до всіх кодів у тому ж модулі / складі. Це знімає потребу в чомусь подібному другові. У Java ключове слово "захищений" поводиться аналогічно wrt доступу з одного пакету.
sellibitze

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

Відповіді:


67

Друзі в програмуванні більш-менш вважаються "брудними" і їх легко зловживати. Це розриває зв’язки між класами і підриває деякі основні ознаки мови ОО.

Як сказано, це приємна особливість, і я багато разів використовував її в C ++; і хотіли б використовувати його і в C #. Але я ставлю на облік через те, що C # 'чиста' OOness (порівняно з псевдо OOness C ++) MS вирішила, що через те, що у Java немає другого ключового слова, C # також не повинен (просто жартує;))

На серйозну увагу: внутрішній не так добре, як друг, але він справді виконує роботу. Пам’ятайте, що рідко ви поширюєте свій код стороннім розробникам не через DLL; тож, поки ви та ваша команда знаєте про внутрішні заняття та їх використання, у вас повинно бути добре.

EDIT Дозвольте мені пояснити, як ключове слово товариша підриває OOP.

Приватні та захищені змінні та методи є, мабуть, однією з найважливіших частин ООП. Ідея про те, що об'єкти можуть містити дані або логіку, яку можуть використовувати лише вони, дозволяє записати свою реалізацію функціональних можливостей незалежно від вашого оточення - і що ваше середовище не може змінювати інформацію про стан, яка не підходить для обробки. Використовуючи другу, ви з'єднуєте реалізацію двох класів разом - що набагато гірше, ніж якщо ви просто з'єднали їх інтерфейс.


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

21
Андерса слід хвалити за риси, які він залишив, а не ті, які він включив.
Мехрдад Афшарі

21
Я не погоджуюся, що внутрішній C # шкодить інкапсуляції. На мою думку, навпаки. Ви можете використовувати його для створення інкапсульованої екосистеми в межах складання. Ряд об'єктів може поділяти внутрішні типи всередині цієї екосистеми, які не піддаються впливу решти програми. Я використовую прийоми складання "товариша" для створення одиниць тестових збірок для цих об'єктів.
Дивовижне

26
Це не має сенсу. friendне допускає довільних класів або функцій доступу до приватних членів Це дозволяє певні функції або класи, які були позначені як товариш, робити це. Клас, що володіє приватним членом, все ще контролює доступ до нього на 100%. Ви можете також стверджувати, що методи громадського членства. Обидва забезпечують, щоб методи, спеціально перелічені в класі, отримали доступ до приватних членів класу.
jalf

8
Я думаю, що навпаки. Якщо в складі є 50 класів, якщо 3 з цих класів використовують якийсь клас корисності, то сьогодні єдиний ваш варіант - позначити цей клас корисності як "внутрішній". Роблячи це, ви не тільки надаєте його для використання для 3 класів, але й для решти класів у складі. Що робити, якщо все, що ви хотіли зробити, - це забезпечити більш тонкий доступ, щоб лише три класи, про які йдеться, мали доступ до класу утиліти. Це фактично робить його більш об'єктно орієнтованим, оскільки покращує інкапсуляцію.
zumalifeguard

104

На бічній записці. Використання друга не полягає в порушенні інкапсуляції, а навпаки, це в тому, щоб це застосувати. Як і аксесуари + мутатори, оператори, що перевантажують, успадковують громадськість, збивають з ладу та ін. , Це часто неправильно використовуються, але це не означає, що ключове слово не має поганого призначення.

Див Konrad Rudolph «s повідомлення в іншому потоці, або , якщо ви віддаєте перевагу бачити відповідний запис в C ++ FAQ.


... та відповідний запис у FQA: yosefk.com/c++fqa/friend.html#fqa-14.2
Джош Лі

28
+1 за " Використання друга - це не про порушення інкапсуляції, а навпаки, це про його
примушення

2
Я думаю, питання семантичної думки; і, можливо, пов'язана з точною ситуацією, про яку йдеться. Створення класу friendдає доступ до ВСІХ ваших приватних членів, навіть якщо вам потрібно дозволити йому встановити одного з них. Слід зробити вагомий аргумент, що надання полів доступним іншому класу, який не потребує їх, вважається "порушенням інкапсуляції". У C ++ є місця, де це необхідно (оператори перевантаження), але є комплантація, що потребує ретельного розгляду. Здається, дизайнери C # вважають, що компроміс цього не вартий.
GrandOpener

3
Інкапсуляція стосується примушування інваріантів. Коли ми визначаємо клас як друг для іншого, ми прямо говоримо, що два класи сильно пов'язані щодо управління інваріантами першого. Зробити знайомого класу все-таки краще, ніж викривати всі атрибути безпосередньо (як публічне поле) або через сеттер.
Люк Ермітт

1
Саме так. Коли ви хочете скористатися другом, але не можете, ви змушені користуватися внутрішніми або громадськими, які набагато відкритіші для того, щоб вас підштовхували речі, які не повинні. Особливо в командному середовищі.
Штрихування

50

Для отримання інформації, інакше пов'язаних, але-ні-зовсім-The-ж саме в .NET є [InternalsVisibleTo], який дозволяє складальному призначити інший вузол (наприклад, випробування складальної одиниці) , що (ефективно) має «внутрішній» доступ до типам / членам в оригінальна збірка.


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

14

Ви повинні мати можливість виконувати ті самі види речей, для яких "друг" використовується в C ++, використовуючи інтерфейси в C #. Це вимагає від вас чітко визначити, які члени передаються між двома класами, що є додатковою роботою, але також може полегшити розуміння коду.

Якщо хтось має приклад розумного використання "друга", який неможливо імітувати за допомогою інтерфейсів, будь ласка, поділіться ним! Я хотів би краще зрозуміти відмінності між C ++ і C #.


Гарна думка. У вас був шанс подивитися на попереднє запитання щодо ПП (запитаний моноксидом та зв'язаним вище?) Мене цікавить, як найкраще вирішити це в C #?
Еш

Я не впевнений, як це зробити в C #, але я думаю, що відповідь Джона Скіта на питання про окис окису в потрібному напрямку. C # дозволяє вкладені класи. Я насправді ще не намагався кодувати і компілювати рішення. :)
Параппа

Я відчуваю, що модель посередника - це хороший приклад того, де приятелю було б добре. Я перетворюю свої форми, щоб мати посередника. Я хочу, щоб моя форма могла називати "події" подібно методам в посереднику, але я не дуже хочу, щоб інші класи називали такі "події" як методи. Функція "Друг" надає формі доступ до методів, відкриваючи її для всього іншого в зборі.
Ваккано

3
Проблема з інтерфейсним підходом заміни "Friend" полягає в тому, що об'єкт може відкрити інтерфейс, це означає, що кожен може передати об'єкт на цей інтерфейс і отримати доступ до методів! Охоплення інтерфейсу до внутрішнього, захищеного чи приватного не працює, як би це працювало, ви можете просто застосувати відповідний метод! Подумайте про матір та дитину в торговому центрі. Громадські - це всі в світі. Внутрішні - це лише люди в торговому центрі. Приватний - це лише мати чи дитина. "Друг" - це щось лише між матір'ю та донькою.
Марк А. Донохое

1
Ось один: stackoverflow.com/questions/5921612/… Оскільки я приходжу з C ++, дефіцит друга зводить мене з розуму майже щодня. За свої кілька років роботи з C # я опублікував буквально сотні методів, опублікованих там, де вони могли бути приватними та безпечнішими, все через брак друга. Особисто я думаю, що друг - це найважливіше, що слід додати до .NET
kaalus

14

З friendдизайнером C ++ точний контроль над тим, кому піддаються приватні * члени. Але він змушений викривати кожного з приватних членів.

З internalС # дизайнером має повний контроль над безліччю приватних членів він оголюючи. Очевидно, він може викрити лише одного приватного члена. Але, воно буде піддаватися всім класам в зборах.

Зазвичай дизайнер бажає викрити лише кілька приватних методів для вибраних кількох інших класів. Наприклад, у фабричній схемі класу може бути бажаним, щоб клас C1 інстанціювався лише класом заводських CF1. Тому клас С1 може мати захищений конструктор та заводський клас CF1.

Як бачите, у нас є два виміри, уздовж яких може бути порушено інкапсуляцію. friendпорушує його вздовж одного виміру, internalробить це уздовж іншого. Яке з них є гіршим порушенням в концепції інкапсуляції? Важко сказати. Але було б непогано мати і те, friendі internalнаявне. Крім того, хорошим доповненням до цих двох буде 3-й тип ключових слів, який буде використовуватися на основі член-член (наприклад internal) і визначає цільовий клас (як-от friend).

* Для стислості я буду використовувати "приватне" замість "приватного та / або захищеного".

- Нік


13

Насправді, C # дає можливість отримати таку саму поведінку чистим способом OOP без спеціальних слів - це приватні інтерфейси.

Що стосується питання Що таке 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 Це дійсно приємне рішення, але я не можу побачити спосіб налагодити цю роботу з глузуючими рамками для тестування одиниць. Будь-які пропозиції?
Стів Ленд

Я трохи розгублений. Якби я хотів, щоб зовнішній клас міг викликати оновлення у вашому першому прикладі (приватний інтерфейс), як би я змінив Controller, щоб дозволити це відбутися?
AnthonyMonterrosa

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

@AnthonyMonterrosa, ми приховуємо оновлення від ВСІХ зовнішніх ресурсів, тож чому ви хочете поділитися цим з кимось;)? Впевнені, що ви можете додати якийсь публічний метод для доступу до приватних членів, але це буде як backdoor для будь-яких інших класів. У будь-якому разі, якщо вам це потрібно для тестування - використовуйте спадкування, щоб зробити щось на зразок класу ControllerTest, щоб зробити там будь-які тестові випадки.
max_cn

9

Друг надзвичайно корисний при написанні одиничного тесту.

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

Дуже корисна та чиста ідіома, яку я знайшов, - це коли я маю фабричні заняття, роблячи їх друзями створених ними предметів, які мають захищений конструктор. Більш конкретно, це було тоді, коли в мене була одна фабрика, відповідальна за створення об'єктів рендеринга для об'єктів запису звітів, надання в задане середовище. У цьому випадку ви маєте єдину точку знань про взаємозв'язок між класами написання звітів (такі речі, як блоки зображень, смуги макетів, заголовки сторінок тощо) та їх відповідні об’єкти візуалізації.


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

9

У C # відсутнє ключове слово "товариш" з тієї ж причини, що його відсутні детерміновані знищення. Зміна конвенцій змушує людей почуватися розумними, ніби їхні нові способи перевершують чужі старі способи. Вся справа в гордості.

Скажімо, що "товариські класи погані" є настільки ж недалекоглядними, як і інші некваліфіковані твердження, такі як "не використовувати gotos" або "Linux краще, ніж Windows".

Ключове слово "друг" у поєднанні з проксі-класом - чудовий спосіб піддавати певні частини класу лише певним іншим класам. Клас проксі може діяти як надійний бар'єр проти всіх інших класів. "public" не дозволяє будь-якого такого націлювання, і використовувати "захищений" для отримання ефекту при спадкуванні незручно, якщо насправді немає концептуальних "відносин".


У C # бракує детермінованого знищення, оскільки це повільніше, ніж збирання сміття. Детерміновані руйнування потребують часу для кожного створеного об'єкта, тоді як для збору сміття потрібен час лише для живих об'єктів, які наразі живуть. У більшості випадків це відбувається швидше, і чим більше короткотривалих об’єктів у системі, тим швидше виходить сміття.
Едвард Робертсон

1
@Edward Robertson Детерміновані руйнування не потребують часу, якщо не визначено деструктора. Але в цьому випадку збирання сміття набагато гірше, тому що тоді потрібно використовувати фіналізатор, який повільніше і не детермінований.
kaalus

1
... а пам'ять - не єдиний вид ресурсу. Люди часто діють так, ніби це було правдою.
cubuspl42

8

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


3
Близько, але не зовсім там. Я думаю, це залежить від того, як ви структуруєте свої збори / класи, але було б більш елегантно мінімізувати кількість класів, яким надається доступ за допомогою друга. Четвертий приклад у складі утиліти / помічника, не всі класи повинні мати доступ до внутрішніх членів.
Еш

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

7

Це фактично не проблема з C #. Це принципове обмеження в ІЛ. C # обмежується цим, як і будь-яка інша мова .Net, яка прагне перевірити. Це обмеження також включає керовані класи, визначені в C ++ / CLI ( Spec розділ 20.5 ).

Попри це, я думаю, що Нельсон має хороше пояснення, чому це погано.


7
-1. friendне погана річ; навпаки, це набагато краще, ніж internal. І пояснення Нельсона є поганим і невірним.
Наваз

4

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

Це для посилення парадигми інкапсуляції? тож вам доведеться писати методи аксесуарів і що тепер? як ви повинні зупинити всіх (крім методів класу В) від виклику цих методів? ти не можеш, тому що ти теж не можеш контролювати це через відсутність "друга".

Жодна мова програмування не є ідеальною. C # - одна з найкращих мов, яку я бачив, але дурні виправдання пропущених функцій нікому не допомагають. У C ++ я сумую за системою легких подій / делегацій, рефлексією (+ автоматична де / серіалізація) та передбачуванням, але в C # я сумую за перевантаженням оператора (так, продовжуйте говорити, що вам це не потрібно), параметри за замовчуванням, const цього неможливо обійти, багаторазове успадкування (так, продовжуйте говорити, що вам це не потрібно, і інтерфейси були достатньою заміною) та можливість вирішити видалити екземпляр із пам'яті (ні, це не жахливо погано, якщо ви не тинкір)


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

2

Існує InternalsVisibleToAttribute з .Net 3, але я підозрюю, що вони додали його лише для задоволення тестових збірок після підйому тестування одиниць. Я не бачу багатьох інших причин використовувати його.

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

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


2

Я прочитав багато розумних коментарів щодо ключового слова "товариш", і я погоджуюся, чим це корисно, але я думаю, що "внутрішнє" ключове слово менш корисне, і вони обоє все ще погані для чистого програмування OO.

Що ми маємо? (кажу про "друга", я також кажу про "внутрішній")

  • використання "friend" робить код менш чистим щодо 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 # generics принципово відрізняється від C ++.


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

1

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


Якщо ви не працюєте над додатком Silverlight.
kaalus

1

Раніше я регулярно користувався другом, і не думаю, що це порушення OOP або ознака будь-якої вади дизайну. Є кілька місць, де це найефективніший засіб для належного завершення з найменшою кількістю коду.

Одним із конкретних прикладів є створення збірок інтерфейсів, які надають комунікаційний інтерфейс деяким іншим програмним забезпеченням. Як правило, є кілька важких класів, які вирішують складність особливостей протоколу та однорангових програм, і забезпечують відносно просту модель підключення / читання / запису / пересилання / відключення, що включає передачу повідомлень та повідомлень між клієнтською програмою та збіркою. Ці повідомлення / сповіщення потрібно упакувати в класи. Атрибутами, як правило, потрібно керувати програмним забезпеченням протоколу, оскільки він є їхнім автором, але багато чого має залишатися лише для читання у зовнішньому світі.

Просто нерозумно заявляти, що це порушення OOP для класу протоколу / "творця", щоб мати інтимний доступ до всіх створених класів - клас творців повинен був трохи підмітати кожен шматочок даних під час руху вгору. Що я вважаю найважливішим - це мінімізувати всі BS додаткові рядки коду, до яких зазвичай призводить модель "OOP for OOP's Sake". Додаткові спагетті просто роблять більше помилок.

Чи знають люди, що ви можете застосувати внутрішнє ключове слово на рівні атрибутів, властивостей та методів? Це не лише для декларації вищого рівня (хоча, здається, більшість прикладів показує це.)

Якщо у вас є клас 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()лише публічний 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);
}

Я вважаю, що щось на зразок друга може бути корисним і реалізованим у мові, не роблячи мову менш "OO", як, можливо, як атрибути, так що ви можете мати класи або методи бути друзями до конкретних класів або методів, що дозволяє розробнику надавати дуже конкретний доступ. Можливо, щось на кшталт ... (псевдо-код)

[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 ++ і ви знаходите себе, використовуючи друге ключове слово, це дуже вагомий показник того, що у вас є проблема з дизайном, тому що для того, щоб чортів класу потрібно отримати доступ до приватних членів іншого класу ??


Я згоден. Мені ніколи не потрібне ключове слово. Зазвичай використовується для вирішення складних проблем технічного обслуговування.
Едуард А. А.

7
Оператор перевантажений, хтось ??
Ми всі Моніка

3
скільки разів ви знаходили хороший випадок для серйозної перевантаження оператора?
bashmohandes

8
Я дам вам дуже простий випадок, який повністю розкриває ваш коментар щодо "дизайну". Батько-дитина. Батько володіє своїми дітьми. Дитина з колекції "Діти" батьків є власником цього батька. Встановлювач батьківського майна на Child не повинен встановлювати ніхто. Її потрібно призначити, коли і лише тоді, коли дитину додають до дитячої колекції батьків. Якщо це публічно, кожен може змінити його. Внутрішній: хтось у цій асамблеї. Приватний? Ніхто. Здійснення того, що сетер є другом для батьків, усуває ці випадки і все ще тримає ваші класи окремими, але тісно пов'язаними. Huzzah !!
Марк А. Донохое

2
Таких випадків дуже багато, як, наприклад, основна схема управління / керованість. Є ще одне питання щодо ТА, і ніхто не придумав способу, як це зробити без друга. Не впевнений, що змушує людей думати, що одного класу "завжди вистачить". Іноді більше одного класу потребує спільної роботи.
kaalus

0

Bsd

Було заявлено, що друзям шкодить чиста ООС. З чим я згоден.

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

Я думаю, що до методології ОО слід додавати дружбу, але не зовсім так, як це в С ++. Я хотів би мати деякі поля / методи, до яких мій клас друзів може отримати доступ, але я НЕ хотів би, щоб вони отримували доступ до ВСІХ моїх полів / методів. Як і в реальному житті, я дозволив друзям отримати доступ до мого особистого холодильника, але я не дав їм отримати доступ до мого банківського рахунку.

Можна реалізувати це, як випливає далі

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

Це, звичайно, генерує попередження компілятора і зашкодить інтелігенції. Але це зробить роботу.

Зі сторони, я думаю, що впевнений програміст повинен зробити тестовий блок, не звертаючись до приватних членів. це зовсім поза сферою, але спробуйте прочитати про 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.