Dlaczego C # nie zapewnia słowa kluczowego „przyjaciel” w stylu C ++? [Zamknięte]


208

W C ++ przyjaciel słów kluczowych pozwala class Awyznaczyć class Bjako przyjaciela. Umożliwia Class Bto dostęp do private/ protectedczłonków class A.

Nigdy nie czytałem niczego, dlaczego zostało to pominięte w C # (i VB.NET). Większość odpowiedzi na to wcześniejsze pytanie StackOverflow wydaje się mówić, że jest to użyteczna część C ++ i istnieją dobre powody, aby z niej korzystać. Z mojego doświadczenia muszę się zgodzić.

Kolejne pytanie wydaje mi się naprawdę pytać, jak zrobić coś podobnego do aplikacji friendw języku C #. Podczas gdy odpowiedzi zazwyczaj dotyczą klas zagnieżdżonych, nie wydaje się to tak eleganckie, jak użycie friendsłowa kluczowego.

Oryginalna książka Design Patterns używa jej regularnie w swoich przykładach.

Podsumowując, dlaczego friendbrakuje go w języku C # i jaki jest sposób (lub sposoby) „najlepszej praktyki” symulowania go w języku C #?

(Nawiasem mówiąc, internalsłowo kluczowe nie jest tym samym, pozwala wszystkim klasom w całym zespole na dostęp do internalelementów, a jednocześnie frienddaje pewnej klasie pełny dostęp do dokładnie jednej innej klasy)


5
czuję się chroniony wewnętrznie to dobry kompromis.
Gulzar Nazim

3
To nie jest zbyt mylące, prawda? Jak powiedziałem powyżej, książka GOF używa jej dość często w przykładach. Dla mnie nie jest to bardziej mylące niż wewnętrzne.
Ash

7
Z pewnością widzę scenariusze, w których mogą być bardzo przydatne, jak już wspomniano Testowanie jednostkowe. Ale czy naprawdę chcesz, aby twoi znajomi mieli dostęp do twoich prywatnych?
danswain

2
Łatwo jest odpowiedzieć: C # oferuje „wewnętrzny” jako modyfikator dostępu, który zapewnia dostęp do całego kodu w tym samym module / zestawie. To eliminuje potrzebę czegoś takiego jak przyjaciel. W Javie słowo kluczowe „chroniony” zachowuje się podobnie do dostępu wrt z tego samego pakietu.
sellibitze

2
@sellibitze, w pytaniu wymieniam różnice z wewnętrznymi. Problem z Interanl polega na tym, że wszystkie klasy w zespole mogą uzyskać dostęp do elementów wewnętrznych. To przerywa enkapsulację, ponieważ wiele z tych klas może nie potrzebować dostępu.
Ash

Odpowiedzi:


67

Posiadanie znajomych w programowaniu jest mniej więcej uważane za „brudne” i łatwe do nadużyć. Łamie relacje między klasami i podważa niektóre podstawowe atrybuty języka OO.

To powiedziawszy, jest to fajna funkcja i używałem jej wiele razy w C ++; i chciałbym użyć go również w języku C #. Ale założę się z powodu „czystego” OOness C # (w porównaniu do pseudoOOness C ++) MS zdecydowało, że ponieważ Java nie ma przyjaciela, słowo kluczowe C # również nie powinno (tylko żartuje;))

Mówiąc poważnie: wewnętrzny nie jest tak dobry jak przyjaciel, ale wykonuje zadanie. Pamiętaj, że rzadko zdarza się, że będziesz dystrybuował kod do zewnętrznych programistów nie za pośrednictwem biblioteki DLL; tak długo, jak ty i twój zespół wiecie o klasach wewnętrznych i ich wykorzystaniu, wszystko będzie dobrze.

EDYCJA Pozwól mi wyjaśnić, w jaki sposób słowo kluczowe znajomego podważa OOP.

Prywatne i chronione zmienne i metody są prawdopodobnie jedną z najważniejszych części OOP. Pomysł, że obiekty mogą przechowywać dane lub logikę, z których tylko one mogą korzystać, pozwala napisać implementację funkcjonalności niezależnie od środowiska - i że środowisko nie może zmieniać informacji o stanie, które nie są odpowiednie do obsługi. Korzystając z przyjaciela, łączysz implementacje dwóch klas razem - co jest znacznie gorsze niż w przypadku połączenia ich interfejsu.


167
C # „wewnętrzny” w rzeczywistości szkodzi enkapsulacji znacznie bardziej niż „przyjaciel” C ++. Dzięki „przyjaźni” właściciel wyraźnie wyraża zgodę na to, kto chce. „Wewnętrzny” to koncepcja otwarta.
Nemanja Trifunovic

21
Anders powinien być pochwalony za funkcje, które pominął, a nie te, które uwzględnił.
Mehrdad Afshari

21
Nie zgadzam się jednak z tym, że wewnętrzne C # szkodzi wypadaniu. Przeciwnie, moim zdaniem. Możesz go użyć do stworzenia enkapsulowanego ekosystemu w zespole. Wiele obiektów może współdzielić typy wewnętrzne w tym ekosystemie, które nie są narażone na resztę aplikacji. Używam sztuczek złożenia „przyjaciela” do tworzenia zespołów testów jednostkowych dla tych obiektów.
Quibblesome

26
To nie ma sensu. friendnie pozwala arbitralnym klasom ani funkcjom na dostęp do prywatnych członków. Pozwala to na wykonanie określonych funkcji lub klas oznaczonych jako przyjaciel. Klasa będąca właścicielem członka prywatnego nadal kontroluje dostęp do niego w 100%. Równie dobrze możesz argumentować, że metody publiczne są członkami. Obie zapewniają, że metody specjalnie wymienione w klasie mają dostęp do prywatnych członków klasy.
lipiec

8
Myślę wręcz przeciwnie. Jeśli zestaw ma 50 klas, jeśli 3 z tych klas używają jakiejś klasy użyteczności, dzisiaj jedyną opcją jest oznaczenie tej klasy użyteczności jako „wewnętrznej”. Robiąc to, udostępniasz go nie tylko 3 klasom, ale także pozostałym klasom w zestawie. Co jeśli wszystko, co chcesz zrobić, to zapewnić bardziej szczegółowy dostęp, aby tylko trzy klasy miały dostęp do klasy użyteczności. W rzeczywistości sprawia, że ​​jest bardziej obiektowy, ponieważ poprawia enkapsulację.
zumalifeguard 30.10.11

104

Na marginesie. Korzystanie ze znajomego nie polega na naruszeniu enkapsulacji, ale wręcz przeciwnie - na jej wymuszeniu. Podobnie jak akcesoria + mutatory, przeciążanie operatorów, dziedziczenie publiczne, downcasting itp. , Często jest niewłaściwie używane, ale nie oznacza to, że słowo kluczowe nie ma, lub gorzej, złego celu.

Zobacz Konrad Rudolph „s wiadomość w innym wątku, lub jeśli wolisz zobaczyć odpowiedni wpis w C ++ FAQ.


... i odpowiedni wpis w FQA
Josh Lee

28
+1 za „ Korzystanie z przyjaciela nie polega na naruszeniu enkapsulacji, ale wręcz przeciwnie - polega na jej wymuszeniu
Nawaz

2
Myślę, że kwestia semantycznej opinii; i być może związane z konkretną sytuacją. Utworzenie klasy a frienddaje dostęp do WSZYSTKICH twoich prywatnych członków, nawet jeśli musisz pozwolić tylko ustawić jednego z nich. Należy podać mocny argument, że udostępnienie pól innej klasie, która ich nie potrzebuje, liczy się jako „naruszenie enkapsulacji”. Są miejsca w C ++, gdzie jest to konieczne (przeciążanie operatorów), ale istnieje kompromis enkapsulacji, który należy dokładnie rozważyć. Wygląda na to, że projektanci C # uznali, że kompromis nie był tego wart.
GrandOpener,

3
Enkapsulacja polega na egzekwowaniu niezmienników. Kiedy definiujemy klasę jako przyjaciela innej, wyraźnie mówimy, że obie klasy są ściśle powiązane w zakresie zarządzania niezmiennikami pierwszej klasy. Zaprzyjaźnienie się z klasą jest nadal lepsze niż ujawnianie wszystkich atrybutów bezpośrednio (jako pola publicznego) lub przez seterów.
Luc Hermitte,

1
Dokładnie. Jeśli chcesz korzystać ze znajomych, ale nie możesz, musisz korzystać z wewnętrznych lub publicznych, które są o wiele bardziej otwarte na zaczepki przez rzeczy, które nie powinny. Zwłaszcza w środowisku zespołowym.
Pisklę

50

Dla informacji, kolejną powiązaną, ale niezupełnie tą samą rzeczą w .NET jest [InternalsVisibleTo], która pozwala zespołowi wyznaczyć inny zespół (taki jak zespół testu jednostkowego), który (skutecznie) ma „wewnętrzny” dostęp do typów / elementów w oryginalny zespół.


3
dobra wskazówka, ponieważ prawdopodobnie ludzie szukający przyjaciela chcą znaleźć sposób na test jednostkowy z możliwością posiadania większej wewnętrznej „wiedzy”.
SwissCoder,

14

Powinieneś być w stanie osiągnąć te same rzeczy, których używa "przyjaciel" w C ++, używając interfejsów w C #. Wymaga to wyraźnego zdefiniowania, które elementy są przekazywane między dwiema klasami, co jest dodatkową pracą, ale może również ułatwić zrozumienie kodu.

Jeśli ktoś ma przykład rozsądnego użycia „przyjaciela”, którego nie można symulować za pomocą interfejsów, udostępnij go! Chciałbym lepiej zrozumieć różnice między C ++ i C #.


Słuszna uwaga. Czy miałeś okazję spojrzeć na wcześniejsze pytanie SO (zadane przez tlenek i powiązane powyżej?) Byłbym zainteresowany, jak najlepiej rozwiązać to w C #?
Ash

Nie jestem pewien, jak to zrobić w języku C #, ale myślę, że odpowiedź Jona Skeeta na pytania dotyczące monotlenku wskazuje we właściwym kierunku. C # pozwala na zagnieżdżone klasy. Jednak tak naprawdę nie próbowałem napisać kodu i skompilować rozwiązania. :)
Parappa

Czuję, że wzór mediatora jest dobrym przykładem tego, gdzie przyjaciel byłby miły. Refaktoryzuję swoje Formularze, aby mieć mediatora. Chcę, aby mój formularz mógł wywoływać metody „zdarzenia” w mediatorze, ale tak naprawdę nie chcę, aby inne klasy nazywały te metody „zdarzeniem”. Funkcja „Przyjaciel” dałaby formularzowi dostęp do metod bez otwierania go na wszystko inne w zespole.
Vaccano,

3
Problem z podejściem interfejsu polegającym na zamianie „Przyjaciela” polega na tym, że obiekt ujawnia interfejs, co oznacza, że ​​każdy może rzutować obiekt na ten interfejs i mieć dostęp do metod! Określenie zakresu interfejsu na wewnętrzny, chroniony lub prywatny nie działa tak, jakby to działało, możesz po prostu określić zakres przedmiotowej metody! Pomyśl o matce i dziecku w centrum handlowym. Publiczność jest wszystkim na świecie. Wewnętrzni to tylko ludzie w centrum handlowym. Prywatne to tylko matka lub dziecko. „Przyjaciel” jest czymś pomiędzy matką i córką.
Mark A. Donohoe,

1
Oto jeden: stackoverflow.com/questions/5921612/... Ponieważ pochodzę z języka C ++, brak przyjaciół doprowadza mnie do szaleństwa prawie codziennie. W ciągu kilku lat pracy z C # podałem do publicznej wiadomości dosłownie setki metod, w których mogą one być prywatne i bezpieczniejsze, a wszystko to z powodu braku przyjaciela. Osobiście uważam, że przyjaciel jest najważniejszą rzeczą, którą należy dodać do .NET
kaalus

14

Dzięki friendC ++ projektant ma precyzyjną kontrolę nad tym, na kogo są narażeni członkowie prywatni *. Ale jest zmuszony zdemaskować każdego z prywatnych członków.

Dzięki internalprojektantowi C # ma precyzyjną kontrolę nad zestawem członków prywatnych, które udostępnia. Oczywiście może ujawnić tylko jednego członka prywatnego. Ale zostanie narażony na wszystkie klasy w zespole.

Zazwyczaj projektant chce udostępnić tylko kilka metod prywatnych wybranym wybranym klasom. Na przykład we wzorcu fabrycznym klasy może być pożądane, aby instancja klasy C1 była tworzona tylko przez klasę fabryczną CF1. Dlatego klasa C1 może mieć chronionego konstruktora i przyjazną fabrykę klasy CF1.

Jak widać, mamy 2 wymiary, wzdłuż których można naruszyć enkapsulację. friendnarusza to wzdłuż jednego wymiaru, internalrobi to wzdłuż drugiego. Które z nich jest gorszym naruszeniem koncepcji enkapsulacji? Ciężko powiedzieć. Ale byłoby miło mieć oba friendi internaldostępne. Ponadto dobrym dodatkiem do tych dwóch byłby trzeci typ słowa kluczowego, który byłby używany na zasadzie członek po członku (podobny internal) i określa klasę docelową (podobny friend).

* Dla zwięzłości użyję słowa „prywatny” zamiast „prywatny i / lub chroniony”.

- Nick


13

W rzeczywistości C # daje możliwość uzyskania tego samego zachowania w czysty sposób OOP bez specjalnych słów - to prywatne interfejsy.

Jeśli chodzi o pytanie Jaki jest odpowiednik C # znajomego?został oznaczony jako duplikat tego artykułu i nikt tam nie proponuje naprawdę dobrej realizacji - w tym miejscu pokażę odpowiedź na oba pytania.

Główna idea wzięła się stąd: czym jest prywatny interfejs?

Powiedzmy, że potrzebujemy klasy, która mogłaby zarządzać instancjami innych klas i wywoływać na nich specjalne metody. Nie chcemy umożliwiać wywoływania tych metod innym klasom. To jest dokładnie to samo, co słowo kluczowe znajomego c ++ robi w świecie c ++.

Myślę, że dobrym przykładem w praktyce może być wzorzec Full State Machine, w którym jakiś kontroler aktualizuje obiekt stanu bieżącego i przełącza w razie potrzeby inny obiekt stanu.

Mógłbyś:

  • Najłatwiejszy i najgorszy sposób upublicznienia metody Update () - mam nadzieję, że wszyscy zrozumieją, dlaczego jest ona zła.
  • Następnym sposobem jest oznaczenie go jako wewnętrznego. Wystarczy, jeśli umieścisz swoje klasy w innym zestawie, ale nawet wtedy każda klasa w tym zestawie może wywołać każdą metodę wewnętrzną.
  • Użyj prywatnego / chronionego interfejsu - i poszedłem w ten sposób.

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

A co z dziedziczeniem?

Musimy zastosować technikę opisaną w części Ponieważ jawne implementacje elementów interfejsu nie mogą być deklarowane jako wirtualne i oznaczać IState jako chronione, aby dać możliwość czerpania również ze sterownika.

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

I na koniec przykład, jak przetestować dziedziczenie klas kontrolerów: ControllerTest.cs

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

Mam nadzieję, że obejmuję wszystkie sprawy i moja odpowiedź była przydatna.


To niesamowita odpowiedź, która wymaga więcej pozytywnych opinii.
KthProg,

@max_cn To naprawdę fajne rozwiązanie, ale nie mogę znaleźć sposobu, aby uzyskać to dzięki szydzącym szkieletom do testowania jednostkowego. Jakieś sugestie?
Steve Land

Jestem nieco zdezorientowany. Jeśli chciałbym, aby klasa zewnętrzna mogła wywołać aktualizację w twoim pierwszym przykładzie (prywatny interfejs), jak zmodyfikowałbym kontroler, aby na to pozwolić?
AnthonyMonterrosa

@Steve Land, możesz użyć dziedziczenia tak, jak pokazano w ControllerTest.cs. Dodaj wszelkie niezbędne metody publiczne do pochodnej klasy testowej, a będziesz mieć dostęp do wszystkich chronionych pracowników, których masz w klasie podstawowej.
max_cn

@AnthonyMonterrosa, ukrywamy aktualizację przed WSZYSTKIM zasobem zewnętrznym, więc dlaczego chcesz się z kimś podzielić;)? Pewnie możesz dodać jakąś publiczną metodę dostępu do prywatnych członków, ale będzie to jak backdoor dla innych klas. W każdym razie, jeśli potrzebujesz go do celów testowych - skorzystaj z dziedziczenia, aby stworzyć coś w rodzaju klasy ControllerTest, aby zrobić tam jakieś przypadki testowe.
max_cn

9

Przyjaciel jest niezwykle przydatny podczas pisania testu jednostkowego.

Chociaż wiąże się to z niewielkim zanieczyszczeniem deklaracji klasy, jest to również wymuszone przez kompilator przypomnienie o tym, jakie testy faktycznie mogą mieć wpływ na wewnętrzny stan klasy.

Bardzo przydatnym i czystym idiomem, który znalazłem, jest posiadanie zajęć fabrycznych, dzięki czemu zaprzyjaźniają się z tworzonymi przez siebie przedmiotami, które mają chronionego konstruktora. Mówiąc dokładniej, wtedy miałem jedną fabrykę odpowiedzialną za tworzenie pasujących obiektów renderujących dla obiektów tworzących raporty, renderowanie w danym środowisku. W tym przypadku masz jeden punkt wiedzy na temat relacji między klasami tworzącymi raporty (takimi jak bloki obrazu, pasy układu, nagłówki stron itp.) I odpowiadającymi im obiektami renderującymi.


Zamierzałem skomentować, że „przyjaciel” jest przydatny na zajęciach w fabryce, ale cieszę się, że ktoś już to zrobił.
Edward Robertson,

9

C # brakuje słowa kluczowego „przyjaciel” z tego samego powodu, dla którego brakuje deterministycznego zniszczenia. Zmieniające się konwencje sprawiają, że ludzie czują się inteligentni, tak jakby ich nowe sposoby były lepsze od starych. Chodzi o dumę.

Mówienie, że „klasy znajomych są złe” jest tak krótkowzroczne, jak inne niekwalifikowane stwierdzenia, takie jak „nie używaj goto” lub „Linux jest lepszy niż Windows”.

Słowo kluczowe „przyjaciel” w połączeniu z klasą proxy jest świetnym sposobem na ujawnienie tylko niektórych części klasy konkretnym innym klasom. Klasa proxy może działać jako zaufana bariera dla wszystkich innych klas. „public” nie zezwala na takie kierowanie, a użycie opcji „chroniony” w celu uzyskania efektu z dziedziczeniem jest niezręczne, jeśli tak naprawdę nie ma pojęciowego „związku”.


C # nie ma deterministycznego zniszczenia, ponieważ jest wolniejszy niż wyrzucanie elementów bezużytecznych. Deterministyczne niszczenie wymaga czasu dla każdego tworzonego obiektu, podczas gdy zbieranie śmieci zajmuje tylko czas dla aktualnie aktywnych obiektów. W większości przypadków jest to szybsze, a im więcej obiektów krótkotrwałych znajduje się w systemie, tym stosunkowo szybsze jest zbieranie śmieci.
Edward Robertson,

1
@Edward Robertson Deterministyczne zniszczenie nie zajmuje czasu, chyba że zdefiniowano destruktor. Ale w tym przypadku odśmiecanie jest znacznie gorsze, ponieważ wtedy trzeba użyć finalizatora, który jest wolniejszy i niedeterministyczny.
kaalus

1
... a pamięć nie jest jedynym rodzajem zasobów. Ludzie często zachowują się tak, jakby to była prawda.
cubuspl42

8

Możesz zbliżyć się do „znajomego” C ++ za pomocą słowa kluczowego C # „internal” .


3
Blisko, ale nie całkiem. Podejrzewam, że zależy to od tego, jak ustrukturyzujesz swoje zespoły / klasy, ale bardziej eleganckie byłoby zminimalizowanie liczby klas, do których dostęp uzyskuje się za pomocą znajomego. Na przykład w zestawie narzędzi / pomocnika nie wszystkie klasy powinny mieć dostęp do elementów wewnętrznych.
Ash

3
@ Ash: Musisz się do tego przyzwyczaić, ale gdy zobaczysz zespoły jako podstawowy element składowy twoich aplikacji, będzie internalto o wiele bardziej sensowne i zapewni doskonały kompromis między enkapsulacją a użytecznością. Domyślny dostęp do Java jest jeszcze lepszy.
Konrad Rudolph

7

W rzeczywistości nie jest to problem z C #. Jest to podstawowe ograniczenie w IL. C # jest przez to ograniczony, podobnie jak każdy inny język .Net, który stara się być weryfikowalny. To ograniczenie obejmuje również klasy zarządzane zdefiniowane w C ++ / CLI ( sekcja Spec 20.5 ).

Biorąc to pod uwagę, myślę, że Nelson ma dobre wytłumaczenie, dlaczego jest to zła rzecz.


7
-1. friendnie jest złą rzeczą; wręcz przeciwnie, jest znacznie lepszy niż internal. Wyjaśnienie Nelsona jest złe i niepoprawne.
Nawaz

4

Przestańcie usprawiedliwiać to ograniczenie. przyjaciel jest zły, ale wewnętrzny jest dobry? są takie same, tylko ten przyjaciel daje ci dokładniejszą kontrolę nad tym, kto ma dostęp, a kto nie.

Ma to na celu egzekwowanie paradygmatu enkapsulacji? więc musisz napisać metody akcesora i co teraz? jak masz powstrzymywać wszystkich (oprócz metod klasy B) przed wywoływaniem tych metod? nie możesz, ponieważ nie możesz tego kontrolować, ponieważ brakuje „przyjaciela”.

Żaden język programowania nie jest idealny. C # jest jednym z najlepszych języków, jakie widziałem, ale głupie usprawiedliwienia dla brakujących funkcji nie pomagają nikomu. W C ++ brakuje mi łatwego systemu zdarzeń / delegowania, refleksji (+ automatycznego de / serializacji) i foreach, ale w C # brakuje mi przeciążenia operatora (tak, mów mi, że go nie potrzebujesz), parametrów domyślnych, stałej którego nie można obejść, wielokrotne dziedziczenie (tak, mów mi, że go nie potrzebujesz, a interfejsy były wystarczającym zamiennikiem) oraz możliwość podjęcia decyzji o usunięciu instancji z pamięci (nie, nie jest to strasznie złe, chyba że jesteś majsterkowicz)


W języku C # można przeciążać większość operatorów. Nie można przeciążać operatorów przydzielania, ale z drugiej strony można nadpisywać ||/ &&utrzymując ich zwarcie. Parametry domyślne zostały dodane w języku C # 4.
CodesInChaos,

2

Istnieje. InternalsVisibleToAttribute od .Net 3, ale podejrzewam, że dodali go tylko do testowania zespołów po wzroście testów jednostkowych. Nie widzę wielu innych powodów, aby z niego korzystać.

Działa na poziomie zespołu, ale wykonuje pracę tam, gdzie nie działa wewnętrzny; oznacza to, że chcesz rozpowszechniać zestaw, ale chcesz, aby inny niepodzielony zespół miał uprzywilejowany dostęp do niego.

Zupełnie słusznie wymagają silnego klucza do zgromadzenia przyjaciół, aby uniknąć tworzenia udawanego przyjaciela obok chronionego zgromadzenia.


2

Przeczytałem wiele sprytnych komentarzy na temat słowa kluczowego „przyjaciel” i zgadzam się, co to jest przydatne, ale myślę, że to „wewnętrzne” słowo kluczowe jest mniej przydatne i oba nadal są złe dla czystego programowania OO.

Co mamy? (mówiąc o „przyjacielu”, mówię także o „wewnętrznym”)

  • czy użycie „przyjaciela” sprawia, że ​​kod jest mniej czysty w odniesieniu do oo?
  • tak;

  • nie użycie „przyjaciela” czyni kod lepszym?

  • nie, nadal musimy nawiązywać prywatne relacje między klasami i możemy to zrobić tylko wtedy, gdy przełamiemy naszą piękną enkapsulację, więc to też nie jest dobre, mogę powiedzieć, że to jeszcze bardziej złe niż użycie „przyjaciela”.

Używanie znajomego powoduje pewne problemy lokalne, nie używanie go powoduje problemy dla użytkowników bibliotek kodów.

wspólne dobre rozwiązanie dla języka programowania widzę tak:

// c++ style
class Foo {
  public_for Bar:
    void addBar(Bar *bar) { }
  public:
  private:
  protected:
};

// c#
class Foo {
    public_for Bar void addBar(Bar bar) { }
}

Co o tym myślisz? Myślę, że to najczęstsze i czysto obiektowe rozwiązanie. Możesz otworzyć dostęp do dowolnej metody dowolnej klasy.


Nie jestem wartownikiem OOP, ale wygląda na to, że rozwiązuje to problemy wszystkich przeciwko przyjacielowi i wewnętrznemu. Ale sugerowałbym użycie atrybutu, aby uniknąć rozszerzenia składni języka C #: [PublicFor Bar] void addBar (Bar bar) {} ... Nie jestem pewien, czy to ma sens; Nie wiem, jak działają atrybuty ani czy mogą one majstrować przy dostępności (robią wiele innych „magicznych” rzeczy).
Grault

2

Odpowiem tylko na pytanie „jak”.

Jest tu tak wiele odpowiedzi, jednak chciałbym zaproponować rodzaj „wzorca projektowego”, aby osiągnąć tę funkcję. Użyję prostego mechanizmu językowego, który obejmuje:

  • Interfejsy
  • Klasa zagnieżdżona

Na przykład mamy 2 główne klasy: studenckie i uniwersyteckie. Student posiada GPA, do którego dostęp ma tylko uniwersytet. Oto kod:

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

Podejrzewam, że ma to coś wspólnego z modelem kompilacji C # - budowaniem IL kompilacji JIT w czasie wykonywania. tj. ten sam powód, dla którego generyczne C # zasadniczo różnią się od generycznych C ++.


Myślę, że kompilator mógłby obsługiwać go dość łatwo przynajmniej między klasami w zestawie. Tylko kwestia relaksującego sprawdzania dostępu dla klasy przyjaciela w klasie docelowej. Przyjaciel między klasami w zespołach zewnętrznych może potrzebować bardziej fundamentalnych zmian w CLR.
Ash

1

możesz zachować prywatność i używać refleksji do wywoływania funkcji. Środowisko testowe może to zrobić, jeśli poprosisz go o przetestowanie funkcji prywatnej


Chyba że pracujesz nad aplikacją Silverlight.
kaalus

1

Zwykle regularnie korzystałem z przyjaciela i nie sądzę, aby było to naruszenie OOP lub oznaka jakiejkolwiek wady projektowej. Jest kilka miejsc, w których jest to najbardziej wydajny sposób na właściwy koniec z najmniejszą ilością kodu.

Jednym konkretnym przykładem jest tworzenie zestawów interfejsów, które zapewniają interfejs komunikacyjny z innym oprogramowaniem. Zasadniczo istnieje kilka klas wagi ciężkiej, które radzą sobie ze złożonością protokołu i specyfikami równorzędnymi, i zapewniają stosunkowo prosty model łączenia / odczytu / zapisu / przekazywania / rozłączania obejmujący przekazywanie wiadomości i powiadomień między aplikacją kliencką a zestawem. Te wiadomości / powiadomienia muszą być zapakowane w klasy. Atrybutami na ogół trzeba manipulować przez oprogramowanie protokołu, ponieważ jest ich twórcą, ale wiele rzeczy musi pozostać tylko do odczytu dla świata zewnętrznego.

Po prostu głupie jest stwierdzenie, że naruszenie protokołu OOP powoduje, że klasa protokołowa / „twórca” ma intymny dostęp do wszystkich utworzonych klas - klasa twórców musiała nieco podszywać się pod każdym względem danych. Najważniejsze dla mnie jest zminimalizowanie wszystkich dodatkowych wierszy kodu BS, do których zwykle prowadzi model „OOP for OOP's Sake”. Dodatkowe spaghetti po prostu powoduje więcej błędów.

Czy ludzie wiedzą, że możesz zastosować wewnętrzne słowo kluczowe na poziomie atrybutu, właściwości i metody? Nie dotyczy to tylko deklaracji klasy najwyższego poziomu (choć wydaje się, że większość przykładów to pokazuje).

Jeśli masz klasę C ++, która używa słowa kluczowego friend i chcesz emulować ją w klasie C #: 1. zadeklaruj publiczną klasę C # 2. zadeklaruj wszystkie atrybuty / właściwości / metody, które są chronione w C ++, a zatem dostępne dla znajomych jako wewnętrzny w C # 3. utwórz właściwości tylko do odczytu dla publicznego dostępu do wszystkich wewnętrznych atrybutów i właściwości

Zgadzam się, że nie jest to w 100% to samo co przyjaciel, a test jednostkowy jest bardzo cennym przykładem potrzeby czegoś takiego jak przyjaciel (podobnie jak kod rejestrujący analizator protokołu). Jednak wewnętrzny zapewnia ekspozycję na klasy, które chcesz mieć, a [InternalVisibleTo ()] obsługuje resztę - wygląda na to, że narodził się specjalnie do testu jednostkowego.

O ile przyjaciel „jest lepszy, ponieważ możesz wyraźnie kontrolować, które klasy mają dostęp” - co do cholery jest bandą podejrzanych złych klas, które robią w tym samym zgromadzeniu? Podziel swoje podziały na partycje!


1

Przyjaźń można symulować, oddzielając interfejsy i implementacje. Pomysł jest następujący: „ Wymagaj konkretnego wystąpienia, ale ogranicz dostęp do budowy tego wystąpienia ”.

Na przykład

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

Pomimo tego, że ItsMeYourFriend()jest to Friendklasa publiczna, może uzyskać do niej dostęp, ponieważ nikt inny nie może uzyskać konkretnego wystąpienia Friendklasy. Ma prywatnego konstruktora, a fabrykaNew() metoda zwraca interfejs.

Zobacz mój artykuł Znajomi i członkowie interfejsu wewnętrznego bezpłatnie z kodowaniem interfejsów, aby uzyskać szczegółowe informacje.


Niestety przyjaźni nie można symulować w żaden możliwy sposób. Zobacz to pytanie: stackoverflow.com/questions/5921612/…
kaalus

1

Niektórzy sugerują, że korzystanie z przyjaciela może wymknąć się spod kontroli. Zgodziłbym się, ale to nie zmniejsza jego przydatności. Nie jestem pewien, czy przyjaciel koniecznie rani paradygmat OO bardziej niż upublicznianie wszystkich członków twojej klasy. Z pewnością język pozwoli upublicznić wszystkich członków, ale jest to zdyscyplinowany programista, który unika tego rodzaju wzorców projektowych. Podobnie zdyscyplinowany programista zastrzegłby używanie przyjaciela w szczególnych przypadkach, w których ma to sens. W niektórych przypadkach wydaje mi się, że wewnętrzne ujawniają się za bardzo. Po co wystawiać klasę lub metodę na wszystko w zestawie?

Mam stronę ASP.NET, która dziedziczy moją własną stronę bazową, która z kolei dziedziczy System.Web.UI.Page. Na tej stronie mam kod, który obsługuje raportowanie błędów użytkownika końcowego dla aplikacji w metodzie chronionej

ReportError("Uh Oh!");

Teraz mam kontrolę użytkownika zawartą na stronie. Chcę, aby kontrola użytkownika mogła wywoływać metody raportowania błędów na stronie.

MyBasePage bp = Page as MyBasePage;
bp.ReportError("Uh Oh");

Nie można tego zrobić, jeśli metoda ReportError jest chroniona. Mogę ustawić go jako wewnętrzny, ale jest on narażony na dowolny kod w zestawie. Chcę tylko, aby był narażony na elementy interfejsu użytkownika, które są częścią bieżącej strony (w tym kontrolki podrzędne). Mówiąc dokładniej, chcę, aby moja podstawowa klasa kontrolna zdefiniowała dokładnie te same metody raportowania błędów i po prostu wywołała metody na stronie podstawowej.

protected void ReportError(string str) {
    MyBasePage bp = Page as MyBasePage;
    bp.ReportError(str);
}

Uważam, że coś takiego jak znajomy może być przydatne i zaimplementowane w języku bez zmniejszania „OO” języka, być może jako atrybutów, dzięki czemu możesz mieć klasy lub metody zaprzyjaźnienia się z konkretnymi klasami lub metodami, umożliwiając programistom określony dostęp. Być może coś w rodzaju ... (pseudo kod)

[Friend(B)]
class A {

    AMethod() { }

    [Friend(C)]
    ACMethod() { }
}

class B {
    BMethod() { A.AMethod() }
}

class C {
    CMethod() { A.ACMethod() }
}

W przypadku mojego poprzedniego przykładu być może mam coś takiego jak poniżej (można argumentować semantykę, ale staram się pojąć ten pomysł):

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

Moim zdaniem koncepcja przyjaciela nie wiąże się z większym ryzykiem niż upublicznienie lub stworzenie publicznych metod lub właściwości dostępu do członków. Jeśli cokolwiek przyjaciel pozwala na inny poziom szczegółowości w dostępie do danych i pozwala zawęzić tę dostępność zamiast poszerzać ją o wewnętrzną lub publiczną.


0

Jeśli pracujesz z C ++ i zauważysz, że używasz słowa kluczowego „przyjaciel”, jest to bardzo silna oznaka, że ​​masz problem z projektem, bo dlaczego, do cholery, klasa musi uzyskać dostęp do prywatnych członków innej klasy?


Zgadzam się. Nigdy nie potrzebuję słowa kluczowego znajomego. Zwykle służy do rozwiązywania złożonych problemów związanych z konserwacją.
Edouard A.

7
Przeciążenie operatora, ktoś?
We Are All Monica,

3
ile razy znalazłeś dobry przypadek poważnego przeciążenia operatora?
bashmohandes

8
Dam ci bardzo prosty przypadek, który całkowicie obala Twój komentarz dotyczący „problemu projektowego”. Rodzic-dziecko. Rodzic jest właścicielem swoich dzieci. Dziecko w kolekcji „Children” rodzica jest własnością tego rodzica. Setter właściwości Parent na Child nie powinien być regulowany przez nikogo. Należy go przypisać tylko wtedy, gdy dziecko zostanie dodane do kolekcji Dzieci rodzica. Jeśli jest to publiczne, każdy może to zmienić. Wewnętrzne: każdy w tym zespole. Prywatny? Nikt. Uczynienie setera przyjacielem dla rodzica eliminuje te przypadki i nadal utrzymuje osobne zajęcia, ale ściśle powiązane. Huzzah !!
Mark A. Donohoe,

2
Istnieje wiele takich przypadków, na przykład najbardziej podstawowy wzorzec menedżera / zarządzanego. Jest jeszcze jedno pytanie dotyczące SO i nikt nie wymyślił, jak to zrobić bez przyjaciela. Nie jestem pewien, co sprawia, że ​​ludzie myślą, że jedna klasa „zawsze wystarczy”. Czasami więcej niż jedna klasa musi współpracować.
kaalus

0

Bsd

Stwierdzono, że przyjaciele ranią czystą OOness. Które zgadzam się.

Stwierdzono również, że przyjaciele pomagają w enkapsulacji, co również zgadzam się.

Myślę, że przyjaźń powinna zostać dodana do metodologii OO, ale nie tak jak w C ++. Chciałbym mieć pewne pola / metody, do których moja klasa przyjaciół ma dostęp, ale NIE chciałbym, aby miały dostęp do WSZYSTKICH moich pól / metod. Tak jak w prawdziwym życiu, pozwalam moim znajomym na dostęp do mojej osobistej lodówki, ale nie pozwalam im na dostęp do mojego konta bankowego.

Można to zaimplementować w następujący sposób

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

To oczywiście wygeneruje ostrzeżenie kompilatora i zaszkodzi inteligencji. Ale to zadziała.

Na marginesie, myślę, że pewien programista powinien wykonać testowanie bez dostępu do prywatnych członków. jest to całkiem poza zakresem, ale spróbuj przeczytać o TDD. jednak jeśli nadal chcesz to zrobić (mając przyjaciół podobnych do c ++), spróbuj czegoś takiego

#if UNIT_TESTING
        public
#else
        private
#endif
            double x;

więc piszesz cały kod bez definiowania UNIT_TESTING, a gdy chcesz przeprowadzić testowanie jednostek, dodajesz #define UNIT_TESTING do pierwszego wiersza pliku (i piszesz cały kod, który wykonuje testowanie jednostek w #if UNIT_TESTING). Z tym należy obchodzić się ostrożnie.

Ponieważ uważam, że testowanie jednostkowe jest złym przykładem użycia znajomych, podam przykład, dlaczego uważam, że przyjaciele mogą być dobrzy. Załóżmy, że masz system łamania (klasę). Z czasem system łamania ulega zużyciu i wymaga remontu. Teraz chcesz, aby naprawił to tylko licencjonowany mechanik. Aby ten przykład był mniej trywialny, powiedziałbym, że mechanik użyłby swojego osobistego (prywatnego) śrubokręta, aby go naprawić. Dlatego klasa mechaników powinna być przyjacielem klasy BreakSystem.


2
Ten parametr FriendClass nie służy jako kontrola dostępu: możesz dzwonić c.MyMethod((FriendClass) null, 5.5, 3)z dowolnej klasy.
Jesse McGrew

0

Przyjaźń można również symulować za pomocą „agentów” - niektórych klas wewnętrznych. Rozważ następujący przykład:

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

Może być znacznie prostsze, gdy jest używany do dostępu tylko do elementów statycznych. Korzyści z takiego wdrożenia polegają na tym, że wszystkie typy są zadeklarowane w wewnętrznym zakresie klas przyjaźni i, w przeciwieństwie do interfejsów, umożliwiają dostęp do elementów statycznych.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.