W C ++ przyjaciel słów kluczowych pozwala class A
wyznaczyć class B
jako przyjaciela. Umożliwia Class B
to dostęp do private
/ protected
czł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 friend
w języku C #. Podczas gdy odpowiedzi zazwyczaj dotyczą klas zagnieżdżonych, nie wydaje się to tak eleganckie, jak użycie friend
słowa kluczowego.
Oryginalna książka Design Patterns używa jej regularnie w swoich przykładach.
Podsumowując, dlaczego friend
brakuje go w języku C # i jaki jest sposób (lub sposoby) „najlepszej praktyki” symulowania go w języku C #?
(Nawiasem mówiąc, internal
słowo kluczowe nie jest tym samym, pozwala wszystkim klasom w całym zespole na dostęp do internal
elementów, a jednocześnie friend
daje pewnej klasie pełny dostęp do dokładnie jednej innej klasy)
Odpowiedzi:
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.
źródło
friend
nie 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.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.
źródło
friend
daje 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.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ół.źródło
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 #.
źródło
Dzięki
friend
C ++ 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
internal
projektantowi 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ę.
friend
narusza to wzdłuż jednego wymiaru,internal
robi to wzdłuż drugiego. Które z nich jest gorszym naruszeniem koncepcji enkapsulacji? Ciężko powiedzieć. Ale byłoby miło mieć obafriend
iinternal
dostę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 (podobnyinternal
) i określa klasę docelową (podobnyfriend
).* Dla zwięzłości użyję słowa „prywatny” zamiast „prywatny i / lub chroniony”.
- Nick
źródło
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ś:
Controller.cs
Program.cs
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
PlayerIdleState.cs
I na koniec przykład, jak przetestować dziedziczenie klas kontrolerów: ControllerTest.cs
Mam nadzieję, że obejmuję wszystkie sprawy i moja odpowiedź była przydatna.
źródło
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.
źródło
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”.
źródło
Możesz zbliżyć się do „znajomego” C ++ za pomocą słowa kluczowego C # „internal” .
źródło
internal
to 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.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.
źródło
friend
nie jest złą rzeczą; wręcz przeciwnie, jest znacznie lepszy niżinternal
. Wyjaśnienie Nelsona jest złe i niepoprawne.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)
źródło
||
/&&
utrzymując ich zwarcie. Parametry domyślne zostały dodane w języku C # 4.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.
źródło
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”)
tak;
nie użycie „przyjaciela” czyni kod lepszym?
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:
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.
źródło
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:
Na przykład mamy 2 główne klasy: studenckie i uniwersyteckie. Student posiada GPA, do którego dostęp ma tylko uniwersytet. Oto kod:
źródło
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 ++.
źródło
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
źródło
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!
źródło
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
Pomimo tego, że
ItsMeYourFriend()
jest toFriend
klasa publiczna, może uzyskać do niej dostęp, ponieważ nikt inny nie może uzyskać konkretnego wystąpieniaFriend
klasy. 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.
źródło
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
Teraz mam kontrolę użytkownika zawartą na stronie. Chcę, aby kontrola użytkownika mogła wywoływać metody raportowania błędów na stronie.
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.
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)
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ł):
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ą.
źródło
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?
źródło
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
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
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.
źródło
c.MyMethod((FriendClass) null, 5.5, 3)
z dowolnej klasy.Przyjaźń można również symulować za pomocą „agentów” - niektórych klas wewnętrznych. Rozważ następujący przykład:
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.
źródło