W C # (i wielu innych językach) dostęp do prywatnych pól innych wystąpień tego samego typu jest całkowicie uzasadniony. Na przykład:
public class Foo
{
private bool aBool;
public void DoBar(Foo anotherFoo)
{
if (anotherFoo.aBool) ...
}
}
Ponieważ specyfikacja C # (sekcje 3.5.1, 3.5.2) stwierdza, że dostęp do pól prywatnych dotyczy typu, a nie wystąpienia. Rozmawiałem o tym z kolegą i próbujemy wymyślić powód, dla którego to działa (zamiast ograniczać dostęp do tej samej instancji).
Najlepszym argumentem, jaki mogliśmy wymyślić, są sprawdzenia równości, w których klasa może chcieć uzyskać dostęp do pól prywatnych, aby określić równość z inną instancją. Czy są jakieś inne powody? Czy może jakiś złoty powód, który absolutnie oznacza, że musi tak działać, czy coś byłoby całkowicie niemożliwe?
Odpowiedzi:
Myślę, że jednym z powodów, dla których działa to w ten sposób, jest to, że modyfikatory dostępu działają w czasie kompilacji . W związku z tym określenie, czy dany obiekt jest również bieżącym obiektem, nie jest łatwe do wykonania. Na przykład rozważ ten kod:
Czy kompilator może koniecznie dowiedzieć się, że
other
tak jestthis
? Nie we wszystkich przypadkach. Można by argumentować, że to po prostu nie powinno się wtedy kompilować, ale oznacza to, że mamy ścieżkę kodu, w której prywatny element członkowski właściwej instancji nie jest dostępny, co moim zdaniem jest jeszcze gorsze.Wymagając tylko zamiast poziomu typu obiekt poziomu widoczności, zapewnia, że problem jest łagodny, a także co do sytuacji, że wydaje się, że powinien działać właściwie pracę.
EDYCJA : Twierdzenie Danilela Hilgartha, że to rozumowanie jest wsteczne, ma wartość. Projektanci języka mogą stworzyć żądany język, a autorzy kompilatorów muszą się do niego dostosować. Biorąc to pod uwagę, projektanci języków mają pewną motywację, aby ułatwić pisarzom kompilatorów wykonywanie ich pracy. (Chociaż w tym przypadku łatwo jest argumentować, że prywatni członkowie mogliby wtedy uzyskać dostęp tylko przez
this
(niejawnie lub jawnie)).Uważam jednak, że to sprawia, że problem jest bardziej zagmatwany niż powinien. Większość użytkowników (łącznie ze mną) uznałoby to za niepotrzebne ograniczenie, gdyby powyższy kod nie działał: w końcu to moje dane, do których próbuję uzyskać dostęp! Dlaczego mam przez to przechodzić
this
?Krótko mówiąc, myślę, że mogłem przesadzić, ponieważ jest to „trudne” dla kompilatora. Tak naprawdę chciałem się zorientować, że powyższa sytuacja wygląda na taką, którą projektanci chcieliby mieć.
źródło
this.bar = 2
ale nieother.bar = 2
, ponieważother
może to być inna instancja.Ponieważ celem rodzaju hermetyzacji używanej w C # i podobnych językach * jest zmniejszenie wzajemnej zależności różnych fragmentów kodu (klas w C # i Javie), a nie różnych obiektów w pamięci.
Na przykład, jeśli piszesz kod w jednej klasie, która używa niektórych pól w innej klasie, to te klasy są bardzo ściśle powiązane. Jeśli jednak masz do czynienia z kodem, w którym masz dwa obiekty tej samej klasy, nie ma dodatkowej zależności. Klasa zawsze zależy od siebie.
Jednak cała ta teoria dotycząca hermetyzacji zawodzi, gdy tylko ktoś utworzy właściwości (lub pary get / set w Javie) i bezpośrednio ujawni wszystkie pola, co sprawia, że klasy są tak sprzężone, jakby i tak uzyskiwały dostęp do pól.
* Wyjaśnienie dotyczące rodzajów kapsułkowania znajduje się w doskonałej odpowiedzi Abla.
źródło
get/set
zostaną udostępnione metody. Metody dostępu nadal zachowują abstrakcję (użytkownik nie musi wiedzieć, że za nim znajduje się pole ani jakie pola mogą znajdować się za właściwością). Na przykład, jeśli piszemyComplex
klasę, możemy ujawnić właściwości, aby uzyskać / ustawić współrzędne biegunowe, a inne pobrać / ustawić dla współrzędnych kartezjańskich. Który z nich jest używany przez klasę pod spodem, jest nieznany użytkownikowi (może to być nawet coś zupełnie innego).private
. Jeśli chcesz, możesz sprawdzić moją odpowiedź na mój punkt widzenia;).Sporo odpowiedzi zostało już dodanych do tego interesującego wątku, jednak nie do końca znalazłem prawdziwy powód, dla którego takie zachowanie jest takie, jakie jest. Spróbuję:
W dawnych czasach
Gdzieś pomiędzy Smalltalk w latach 80-tych a Javą w połowie lat 90-tych dojrzewała koncepcja orientacji obiektowej. Ukrywanie informacji, które pierwotnie nie było uważane za koncepcję dostępną tylko dla OO (wspomniane po raz pierwszy w 1978 r.), Zostało wprowadzone w Smalltalk, ponieważ wszystkie dane (pola) klasy są prywatne, wszystkie metody są publiczne. Podczas wielu nowych opracowań OO w latach 90-tych Bertrand Meyer próbował sformalizować wiele koncepcji OO w swojej przełomowej książce Object Oriented Software Construction (OOSC), która od tego czasu jest uważana za (prawie) ostateczne odniesienie do koncepcji OO i projektowania języka .
W przypadku prywatnej widoczności
Według Meyera metoda powinna być dostępna dla określonego zbioru klas (str. 192-193). Daje to oczywiście bardzo dużą szczegółowość ukrywania informacji, następująca funkcja jest dostępna dla klasy A i klasy B oraz wszystkich ich potomków:
W przypadku
private
on mówi co następuje: bez jawnego zadeklarowania typu jako widocznego dla własnej klasy, nie możesz uzyskać dostępu do tej cechy (metody / pola) w wywołaniu kwalifikowanym. Tox
znaczy, jeśli jest zmienną,x.doSomething()
nie jest dozwolone. Niekwalifikowany dostęp jest oczywiście dozwolony wewnątrz samej klasy.Innymi słowy: aby zezwolić na dostęp instancji tej samej klasy, musisz jawnie zezwolić tej klasie na dostęp do metody. Jest to czasami nazywane prywatnymi instancjami i klasami prywatnymi.
Instancja prywatna w językach programowania
Znam co najmniej dwa obecnie używane języki, w których ukrywa się prywatne informacje instancji w przeciwieństwie do ukrywania prywatnych informacji klasowych. Jednym z nich jest Eiffel, język zaprojektowany przez Meyera, który przenosi OO do skrajności. Drugim jest Ruby, obecnie o wiele bardziej powszechny język. W Rubim
private
oznacza: „prywatny dla tej instancji” .Wybory dotyczące projektowania języka
Sugerowano, że zezwolenie na prywatność instancji byłoby trudne dla kompilatora. Nie sądzę, ponieważ stosunkowo łatwo jest po prostu zezwolić lub zabronić kwalifikowanych wywołań metod. Jeśli w przypadku metody prywatnej
doSomething()
jest to dozwolone, ax.doSomething()
nie, projektant języka skutecznie zdefiniował dostępność tylko dla instancji dla prywatnych metod i pól.Z technicznego punktu widzenia nie ma powodu, aby wybierać jedną lub drugą stronę (zwłaszcza biorąc pod uwagę, że Eiffel.NET może to zrobić z IL, nawet w przypadku dziedziczenia wielokrotnego, nie ma żadnego powodu, aby nie zapewniać tej funkcji).
Oczywiście jest to kwestia gustu i jak już inni wspomnieli, całkiem niektóre metody mogą być trudniejsze do napisania bez funkcji widoczności na poziomie klasy prywatnych metod i pól.
Dlaczego C # umożliwia tylko hermetyzację klas, a nie hermetyzację wystąpień
Jeśli spojrzysz na wątki internetowe dotyczące hermetyzacji instancji (termin używany czasami w odniesieniu do faktu, że język definiuje modyfikatory dostępu na poziomie instancji, a nie na poziomie klasy), koncepcja ta jest często źle widziana. Jednak biorąc pod uwagę, że niektóre współczesne języki używają enkapsulacji instancji, przynajmniej dla modyfikatora dostępu prywatnego, sprawia, że myślisz, że może być i jest użyteczne we współczesnym świecie programowania.
Jednak C # najwyraźniej najtrudniej przyjrzał się C ++ i Javie ze względu na projekt języka. Podczas gdy Eiffel i Modula-3 również były na obrazku, biorąc pod uwagę wiele brakujących funkcji Eiffla (dziedziczenie wielokrotne), uważam, że wybrali tę samą trasę, co Java i C ++, jeśli chodzi o modyfikator dostępu prywatnego.
Jeśli naprawdę chcesz wiedzieć, dlaczego powinieneś spróbować skontaktować się z Ericiem Lippertem, Krzysztofem Cwaliną, Andersem Hejlsbergiem lub kimkolwiek innym, kto pracował nad standardem C #. Niestety, nie mogłem znaleźć ostatecznej notatki w opatrzonym adnotacjami języku programowania C # .
źródło
Foo
skutecznie reprezentowała interfejs i klasę implementującą; ponieważ COM nie miał koncepcji odwołania do obiektu klasy - tylko odwołania do interfejsu - nie było możliwości, aby klasa mogła przechowywać odniesienie do czegoś, co było gwarantowane jako kolejna instancja tej klasy. Ten projekt oparty na interfejsie utrudniał niektóre rzeczy, ale oznaczał, że można zaprojektować klasę, którą można zastąpić inną, bez konieczności współużytkowania którejkolwiek z tych samych elementów wewnętrznych.To tylko moja opinia, ale pragmatycznie myślę, że jeśli programista ma dostęp do źródła klasy, można rozsądnie zaufać mu w dostępie do prywatnych członków instancji klasy. Po co wiązać prawą rękę programisty, skoro w lewej dałeś im już klucze do królestwa?
źródło
Przyczyną jest rzeczywiście sprawdzanie równości, porównywanie, klonowanie, przeciążanie operatorów ... Na przykład wprowadzenie operatora + na liczbach zespolonych byłoby bardzo trudne.
źródło
readonly
a następnie dotyczy to również deklarującego wystąpienia. Najwyraźniej zapewniasz, że powinna istnieć określona reguła kompilatora, która zabrania tego, ale każda taka reguła jest funkcją, którą zespół C # musi określić, udokumentować, zlokalizować, przetestować, utrzymywać i wspierać. Jeśli nie ma nieodpartej korzyści, dlaczego mieliby to robić?Po pierwsze, co by się stało z prywatnymi statycznymi członkami? Czy można uzyskać do nich dostęp tylko za pomocą metod statycznych? Na pewno byś tego nie chciał, ponieważ wtedy nie byłbyś w stanie uzyskać dostępu do swojego
const
plików.Jeśli chodzi o twoje wyraźne pytanie, rozważ przypadek a
StringBuilder
, który jest zaimplementowany jako połączona lista własnych instancji:Jeśli nie możesz uzyskać dostępu do prywatnych członków innych instancji Twojej własnej klasy, musisz zaimplementować w
ToString
ten sposób:To zadziała, ale to O (n ^ 2) - niezbyt wydajne. W rzeczywistości to prawdopodobnie podważa cały cel posiadania
StringBuilder
klasy na pierwszym miejscu. Jeśli możesz uzyskać dostęp do prywatnych członków innych instancji Twojej własnej klasy, możesz to zaimplementowaćToString
, tworząc ciąg o odpowiedniej długości, a następnie wykonując niebezpieczną kopię każdego fragmentu w odpowiednim miejscu w ciągu:Ta implementacja to O (n), dzięki czemu jest bardzo szybka i jest możliwa tylko wtedy, gdy masz dostęp do prywatnych członków innych instancji Twojej klasy .
źródło
internal
sprawia, że jest ono mniej prywatne! Skończyło się na tym, że wystawiałbyś wnętrze swojej klasy na wszystko, co się składa.Jest to całkowicie uzasadnione w wielu językach (C ++ na przykład). Modyfikatory dostępu pochodzą z zasady hermetyzacji w OOP. Chodzi o to, aby ograniczyć dostęp do zewnątrz , w tym przypadku na zewnątrz są inne klasy. Na przykład każda klasa zagnieżdżona w C # może również uzyskać dostęp do swoich prywatnych członków nadrzędnych.
Chociaż jest to wybór projektu dla projektanta języka. Ograniczenie tego dostępu może bardzo skomplikować niektóre bardzo typowe scenariusze, nie przyczyniając się zbytnio do izolacji jednostek.
Jest podobna dyskusja tutaj
źródło
Nie sądzę, żeby istniał powód, dla którego nie moglibyśmy dodać kolejnego poziomu prywatności, w którym dane są prywatne w każdej instancji. W rzeczywistości może to nawet zapewnić przyjemne poczucie kompletności językowi.
Ale w praktyce wątpię, żeby to było naprawdę przydatne. Jak zauważyłeś, nasza zwykła prywatność jest przydatna do rzeczy takich jak sprawdzanie równości, a także do większości innych operacji obejmujących wiele instancji typu. Chociaż podoba mi się również twój punkt widzenia dotyczący utrzymania abstrakcji danych, ponieważ jest to ważny punkt w OOP.
Myślę dookoła, zapewnienie możliwości ograniczenia dostępu w taki sposób może być fajną funkcją do dodania do OOP. Czy to naprawdę przydatne? Powiedziałbym, że nie, ponieważ klasa powinna móc ufać własnemu kodowi. Ponieważ ta klasa jest jedyną rzeczą, która ma dostęp do prywatnych elementów członkowskich, nie ma prawdziwego powodu, aby potrzebować abstrakcji danych w przypadku wystąpienia innej klasy.
Oczywiście zawsze możesz napisać swój kod tak, jakby był prywatny zastosowany do instancji. Użyj zwykłych
get/set
metod, aby uzyskać dostęp do danych / zmienić je. To prawdopodobnie uczyniłoby kod łatwiejszym w zarządzaniu, gdyby klasa mogła podlegać wewnętrznym zmianom.źródło
Świetne odpowiedzi podane powyżej. Dodałbym, że częścią tego problemu jest fakt, że tworzenie instancji klasy wewnątrz siebie jest nawet dozwolone. To sprawia, że na przykład w pętli rekurencyjnej "for" używa się tego typu sztuczek, o ile masz logikę do zakończenia rekurencji. Ale tworzenie instancji lub przekazywanie tej samej klasy w sobie bez tworzenia takich pętli logicznie stwarza własne niebezpieczeństwa, mimo że jest to powszechnie akceptowany paradygmat programowania. Na przykład klasa C # może utworzyć wystąpienie swojej kopii w domyślnym konstruktorze, ale nie łamie to żadnych reguł ani nie tworzy pętli przyczynowych. Czemu?
A tak przy okazji… ten sam problem dotyczy również członków „chronionych”. :(
Nigdy w pełni nie zaakceptowałem tego paradygmatu programowania, ponieważ nadal wiąże się on z całym zestawem problemów i zagrożeń, których większość programistów nie pojmuje w pełni, dopóki problemy takie jak ten problem nie pojawią się i nie zmylą ludzi i nie zaprzeczy całemu powodowi posiadania prywatnych członków.
Ten „dziwny i zwariowany” aspekt C # to jeszcze jeden powód, dla którego dobre programowanie nie ma nic wspólnego z doświadczeniem i umiejętnościami, a jedynie znajomość sztuczek i pułapek… jak praca przy samochodzie. To argument, że zasady miały zostać złamane, co jest bardzo złym modelem dla każdego języka komputerowego.
źródło
Wydaje mi się, że gdyby dane były prywatne dla innych instancji tego samego typu, niekoniecznie byłyby już tego samego typu. Wydaje się, że nie zachowuje się ani nie działa tak samo jak inne instancje. Zachowanie można łatwo zmodyfikować na podstawie tych prywatnych danych wewnętrznych. Moim zdaniem to po prostu wywołałoby zamieszanie.
Mówiąc luźno, osobiście uważam, że pisanie klas wywodzących się z klasy bazowej oferuje podobną funkcjonalność, którą opisujesz jako „posiadanie prywatnych danych na instancję”. Zamiast tego masz po prostu nową definicję klasy dla „unikalnego” typu.
źródło