Jest to dla mnie bardzo interesujące, którego zalety dają „globalną klasę root” dla frameworka. Krótko mówiąc, jakie były przyczyny tego, że środowisko .NET zostało zaprojektowane tak, aby miało jedną klasę obiektów głównych z ogólną funkcjonalnością odpowiednią dla wszystkich klas.
Obecnie projektujemy nową platformę do użytku wewnętrznego (platforma w ramach platformy SAP) i wszyscy podzieliliśmy się na dwa obozy - pierwszy, który uważa, że ten szkielet powinien mieć globalny katalog główny, a drugi - kto myśli inaczej.
Jestem na obozie „Global root”. A moje powody, jakie to podejście zapewniłoby dobrą elastyczność i redukcję kosztów rozwoju, ponieważ nie będziemy już rozwijać ogólnej funkcjonalności.
Jestem więc bardzo zainteresowany, aby dowiedzieć się, jakie powody skłaniają architektów .NET do takiego projektowania ram.
źródło
object
systemie .NET istnieje po części root, ponieważ zapewnia on pewne podstawowe możliwości dla wszystkich obiektów, takich jakToString()
iGetHashCode()
wait()
/notify()
/notifyAll()
iclone()
.Odpowiedzi:
Najbardziej palącą przyczyną
Object
są pojemniki (przed lekami generycznymi), które mogą zawierać cokolwiek zamiast iść w stylu C „Napisz to wszystko, czego potrzebujesz”. Oczywiście, pomysł, że wszystko powinno odziedziczyć po określonej klasie, a następnie nadużywanie tego faktu, aby całkowicie stracić każdą plamkę bezpieczeństwa typu, jest tak straszny, że powinien być gigantyczną lampką ostrzegawczą przed wysyłką języka bez leków generycznych, a także oznacza żeObject
jest dokładnie redundantny dla nowego kodu.źródło
Object
, a którego nie można by napisaćvoid *
w C ++?void*
jest coś lepszego? To nie jest Jeśli cokolwiek. jest jeszcze gorzej .void*
jest gorszy w C ze wszystkimi niejawnymi rzutami wskaźnika, ale C ++ jest bardziej rygorystyczny pod tym względem. Przynajmniej musisz rzucić jawnie.Pamiętam, jak kiedyś Eric Lippert powiedział, że dziedziczenie po
System.Object
klasie zapewnia „najlepszą wartość dla klienta”. [edytuj: tak, powiedział to tutaj ]:Posiadanie wszystkiego, co wywodzi się z
System.Object
klasy, zapewnia niezawodność i użyteczność, którą bardzo szanowałem. Wiem, że wszystkie obiekty będą miały typ (GetType
), że będą zgodne z metodami finalizacji CLR (Finalize
) i że mogęGetHashCode
z nich korzystać podczas obsługi kolekcji.źródło
GetType
, możesz po prostu rozszerzyć,typeof
aby zastąpić jego funkcjonalność. Jeśli chodzi o toFinalize
, w rzeczywistości wiele typów nie jest w ogóle finalizowalnych i nie ma sensownej metody finalizacji. Ponadto wiele typów nie jest haszowalnych ani konwertowalnych na ciąg znaków - szczególnie typy wewnętrzne, które nigdy nie są przeznaczone do takich celów i nigdy nie są udostępniane do użytku publicznego. Wszystkie te typy mają niepotrzebnie zanieczyszczone interfejsy.GetType
,Finalize
lubGetHashCode
dla każdegoSystem.Object
. Powiedziałem tylko, że są tam, jeśli ich chcesz. Jeśli chodzi o „ich niepotrzebnie zanieczyszczone interfejsy”, odniosę się do mojego pierwotnego cytatu elippert: „najlepsza wartość dla klienta”. Oczywiście zespół .NET był gotów poradzić sobie z kompromisami.Myślę, że projektanci Java i C # dodali obiekt root, ponieważ był on dla nich w zasadzie darmowy, jak na darmowym obiedzie .
Koszty dodania obiektu głównego są bardzo równe kosztom dodania pierwszej funkcji wirtualnej. Ponieważ zarówno CLR, jak i JVM są środowiskami śmieciowymi z finalizatorami obiektów, musisz mieć co najmniej jeden wirtualny dla twojej
java.lang.Object.finalize
lub dla twojejSystem.Object.Finalize
metody. W związku z tym koszty dodania obiektu głównego są już opłacone, możesz uzyskać wszystkie korzyści bez płacenia za to. To jest najlepsze z obu światów: użytkownicy, którzy potrzebują wspólnej klasy root, dostaną to, czego chcą, a użytkownicy, którym nie zależy to mniej, będą mogli programować tak, jakby ich nie było.źródło
Wydaje mi się, że masz tutaj trzy pytania: jedno dotyczy tego, dlaczego wspólny root został wprowadzony do .NET, drugie to zalety i wady tego, a trzecie to, czy dobrym pomysłem jest, aby element ramowy miał globalny korzeń.
Dlaczego .NET ma wspólny katalog główny?
W najbardziej technicznym sensie posiadanie wspólnego pierwiastka jest niezbędne dla odbicia i dla pojemników pre-generycznych.
Ponadto, o ile mi wiadomo, posiadanie wspólnego katalogu głównego z metodami podstawowymi, takimi jak
equals()
ihashCode()
został bardzo pozytywnie odebrany w Javie, a na C # miał wpływ (między innymi) Java, więc chcieli mieć tę funkcję również.Jakie są zalety i wady posiadania wspólnego katalogu głównego w języku?
Zaleta posiadania wspólnego katalogu głównego:
equals()
ihashCode()
. Oznacza to na przykład, że każdy obiekt w Javie lub C # może być używany w mapie skrótu - porównaj tę sytuację z C ++.printf
metody podobnej do.object
. Być może niezbyt powszechne, ale bez wspólnego korzenia nie byłoby to możliwe.Wada posiadania wspólnego katalogu głównego:
Czy powinieneś korzystać ze wspólnego katalogu głównego w swoim projekcie ramowym?
Oczywiście bardzo subiektywnie, ale kiedy spojrzę na powyższą listę pro-conów, zdecydowanie odpowiem tak . W szczególności ostatni punkt na liście pro - elastyczność późniejszej zmiany zachowania wszystkich obiektów przez zmianę katalogu głównego - staje się bardzo przydatny na platformie, na której zmiany w katalogu głównym są częściej akceptowane przez klientów niż zmiany całej język.
Poza tym - choć jest to jeszcze bardziej subiektywne - uważam, że koncepcja wspólnego korzenia jest bardzo elegancka i pociągająca. Ułatwia także korzystanie z niektórych narzędzi, na przykład teraz można łatwo poprosić narzędzie, aby pokazało wszystkich potomków tego wspólnego katalogu głównego i otrzymało szybki, dobry przegląd typów frameworka.
Oczywiście typy muszą być do tego co najmniej nieznacznie powiązane, aw szczególności nigdy nie poświęciłbym zasady „jest”, aby mieć wspólny rdzeń.
źródło
Matematycznie bardziej elegancko jest mieć system typów zawierający top i pozwalający na pełniejsze zdefiniowanie języka.
Jeśli tworzysz framework, rzeczy z pewnością zostaną zużyte przez istniejącą bazę kodu. Nie możesz mieć uniwersalnego nadtypu, ponieważ istnieją wszystkie inne typy, niezależnie od tego, co zużywa framework.
W tym momencie decyzja o wspólnej klasie bazowej zależy od tego, co robisz. Bardzo rzadko zdarza się, że wiele rzeczy ma wspólne zachowanie i przydatne jest odwoływanie się do tych rzeczy za pomocą samego wspólnego zachowania.
Ale to się zdarza. Jeśli tak, to od razu wyodrębnij to wspólne zachowanie.
źródło
Przekazano obiekt główny, ponieważ chcieli, aby wszystkie klasy w ramach obsługiwały pewne rzeczy (uzyskanie kodu skrótu, konwersja na ciąg znaków, kontrole równości itp.). Zarówno C #, jak i Java uznają za przydatne umieszczenie wszystkich tych wspólnych funkcji Object w jakiejś klasie root.
Pamiętaj, że nie naruszają one żadnych zasad OOP ani niczego. Wszystko w klasie Object ma sens w dowolnej podklasie (czytaj: dowolna klasa) w systemie. Jeśli zdecydujesz się przyjąć ten projekt, upewnij się, że postępujesz zgodnie z tym wzorem. Oznacza to, że nie należy umieszczać w katalogu głównym rzeczy, które nie należą do każdej pojedynczej klasy w systemie. Jeśli dokładnie przestrzegasz tej zasady, nie widzę żadnego powodu, dla którego nie powinieneś mieć klasy root, która zawiera użyteczny, wspólny kod dla całego systemu.
źródło
Kilka powodów, wspólna funkcjonalność, którą mogą udostępniać wszystkie klasy potomne. Dało to także twórcom frameworka możliwość napisania wielu innych funkcji do frameworka, z których wszystko mogłoby korzystać. Przykładem może być buforowanie i sesje ASP.NET. Prawie wszystko może być w nich przechowywane, ponieważ napisali metody dodawania do akceptowania obiektów.
Klasa root może być bardzo pociągająca, ale bardzo, bardzo łatwo ją niewłaściwie wykorzystać. Czy zamiast tego można mieć interfejs użytkownika root? I masz tylko jedną lub dwie małe metody? A może dodałoby to o wiele więcej kodu niż jest to wymagane w twoim systemie?
Pytam, jestem ciekawy, jaką funkcjonalność musisz udostępnić wszystkim możliwym klasom w pisanym frameworku. Czy to naprawdę wykorzystają wszystkie obiekty? Lub przez większość z nich? A kiedy utworzysz klasę root, jak zapobiegniesz dodawaniu do niej losowych funkcji? Lub zmienne losowe, które chcą być „globalne”?
Kilka lat temu była bardzo duża aplikacja, w której utworzyłem klasę root. Po kilku miesiącach opracowywania został wypełniony kodem, w którym nie było biznesu. Konwertowaliśmy starą aplikację ASP, a klasa główna zastąpiła stary plik global.inc, którego używaliśmy w przeszłości. Musiałem nauczyć się tej lekcji na własnej skórze.
źródło
Wszystkie autonomiczne obiekty sterty dziedziczą
Object
; ma to sens, ponieważ wszystkie autonomiczne obiekty sterty muszą mieć pewne wspólne aspekty, takie jak sposób identyfikacji ich typu. W przeciwnym razie, jeśli śmieciarz miałby odwołanie do obiektu sterty nieznanego typu, nie miałby możliwości dowiedzenia się, jakie bity w obiekcie blob pamięci związanym z tym obiektem powinny być traktowane jako odniesienia do innych obiektów sterty.Ponadto w systemie typów wygodnie jest używać tego samego mechanizmu do definiowania elementów struktur i elementów klas. Zachowanie lokalizacji pamięci typu wartości (zmiennych, parametrów, pól, miejsc tablic itp.) Jest bardzo różne od lokalizacji pamięci typu klasy, ale takie różnice behawioralne są osiągane w kompilatorach kodu źródłowego i silniku wykonawczym (w tym kompilator JIT), a nie wyrażany w systemie typów.
Jedną z konsekwencji tego jest to, że zdefiniowanie typu wartości skutecznie definiuje dwa typy - typ lokalizacji magazynu i typ obiektu sterty. Pierwsze z nich może być domyślnie przekonwertowane na drugie, a drugie może zostać przekonwertowane na pierwsze za pomocą typecastu. Oba typy konwersji działają poprzez kopiowanie wszystkich pól publicznych i prywatnych z jednej instancji danego typu do drugiej. Ponadto możliwe jest użycie ogólnych ograniczeń do bezpośredniego wywoływania elementów interfejsu w lokalizacji pamięci typu wartość, bez uprzedniego wykonania jej kopii.
Wszystko to jest ważne, ponieważ odwołania do obiektów sterty typu wartości zachowują się jak odwołania do klas, a nie jak typy wartości. Rozważmy na przykład następujący kod:
Jeśli
testEnumerator()
metoda zostanie przekazana, miejsce przechowywania typu wartościit
otrzyma instancję, której pola publiczne i prywatne zostaną skopiowane z przekazanej wartości. Zmienna lokalnait2
przechowa inną instancję, z której wszystkie pola zostaną skopiowaneit
. DzwoniącMoveNext
nait2
nie wpłynieit
.Jeśli powyższy kod zostanie przekazany do miejsca przechowywania typu klasy, wówczas przekazana wartość
it
iit2
wszystkie będą odnosić się do tego samego obiektu, a zatem wywołanieMoveNext()
dowolnego z nich spowoduje skuteczne wywołanie go na wszystkich z nich.Zauważ, że rzutowanie w
List<String>.Enumerator
celuIEnumerator<String>
skutecznego przekształca go z typu wartości na typ klasy. Typ obiektu sterty jest,List<String>.Enumerator
ale jego zachowanie będzie się bardzo różnić od typu wartości o tej samej nazwie.źródło
List<T>.Enumerator
który jest przechowywany jako a,List<T>.Enumerator
znacznie różni się od zachowania tego, które jest przechowywane w tymIEnumerator<T>
, że przechowywanie wartości poprzedniego typu w miejscu przechowywania dowolnego typu utworzy kopię stanu modułu wyliczającego, podczas zapisywania wartości tego drugiego typu w miejscu przechowywania, które również jest tego drugiego typu, nie utworzy kopii.Object
ma to nic wspólnego z tym faktem.System.Int32
jest również instancją typuSystem.Object
, ale lokalizacja pamięci typuSystem.Int32
nie zawiera aniSystem.Object
instancji, ani odwołania do niej.Ten projekt naprawdę nawiązuje do Smalltalk, co w dużej mierze uważałbym za próbę orientacji na obiekt kosztem niemal wszystkich innych problemów. Jako taki, moim zdaniem, ma tendencję do używania orientacji obiektowej, nawet jeśli inne techniki są prawdopodobnie (a nawet na pewno) lepsze.
Posiadanie jednej hierarchii z
Object
(lub czymś podobnym) w katalogu głównym sprawia, że dość łatwe (na przykład) tworzenie klas kolekcji jako kolekcji jestObject
więc banalne, aby kolekcja zawierała dowolny rodzaj obiektu.W zamian za tę raczej niewielką przewagę dostajesz wiele wad. Po pierwsze, z punktu widzenia projektu, masz naprawdę szalone pomysły. Co, zgodnie z poglądem Java na wszechświat, co łączy ateizm i las? Oboje mają kody skrótu! Czy mapa to kolekcja? Według Javy nie, nie jest!
W latach 70., kiedy projektowano Smalltalk, tego rodzaju bzdury zostały zaakceptowane, przede wszystkim dlatego, że nikt nie opracował rozsądnej alternatywy. Smalltalk został sfinalizowany w 1980 r., A do 1983 r. Opracowano Adę (która obejmuje generyczne). Chociaż Ada nigdy nie osiągnęła takiej popularności, jaką niektórzy przewidywali, jej ogólne cechy były wystarczające do obsługi zbiorów obiektów dowolnych typów - bez szaleństwa nieodłącznego od monolitycznych hierarchii.
Kiedy zaprojektowano Javę (i, w mniejszym stopniu, .NET), monolityczną hierarchię klas prawdopodobnie postrzegano jako „bezpieczny” wybór - z problemami, ale najczęściej znanymi problemami. Natomiast programowanie ogólne było takie, które prawie wszyscy (nawet wtedy) zdawało sobie sprawę, było przynajmniej teoretycznie znacznie lepsze podejście do problemu, ale takie, które wielu komercyjnych deweloperów uważało za raczej słabo zbadane i / lub ryzykowne (tj. W świecie komercyjnym , Ada została w dużej mierze odrzucona jako porażka).
Pozwólcie, że wyrażę się jasno: monolityczna hierarchia była błędem. Przyczyny tego błędu były co najmniej zrozumiałe, ale i tak był to błąd. To zły projekt, a jego problemy projektowe przenikają prawie cały kod, który go używa.
W przypadku nowego projektu nie ma jednak rozsądnego pytania: stosowanie monolitycznej hierarchii jest oczywistym błędem i złym pomysłem.
źródło
To, co chcesz zrobić, jest naprawdę interesujące, trudno jest udowodnić, czy jest źle, czy dobrze, dopóki nie skończysz.
Oto kilka rzeczy do przemyślenia:
Są to jedyne dwie rzeczy, które mogą czerpać korzyści ze wspólnego katalogu głównego. W języku służy do zdefiniowania kilku bardzo powszechnych metod i umożliwia przekazywanie obiektów, które nie zostały jeszcze zdefiniowane, ale nie powinny mieć zastosowania.
Ponadto z własnego doświadczenia:
Użyłem kiedyś zestawu narzędzi napisanego przez programistę, którego tłem było Smalltalk. Sprawił, że wszystkie jego klasy „Data” rozszerzyły jedną klasę, a wszystkie metody zajęły tę klasę - jak dotąd tak dobrze. Problem polega na tym, że różne klasy danych nie zawsze były wymienne, więc teraz mój edytor i kompilator nie mógł udzielić ŻADNEJ pomocy co do tego, co należy przejść do danej sytuacji, i musiałem odwołać się do jego dokumentów, które nie zawsze pomagały. .
To była najtrudniejsza w użyciu biblioteka, z jaką kiedykolwiek miałem do czynienia.
źródło
Ponieważ taka jest metoda OOP. Ważne jest, aby mieć typ (obiekt), który może odnosić się do wszystkiego. Argument typu obiekt może zaakceptować wszystko. Istnieje również dziedziczenie, możesz być pewien, że ToString (), GetHashCode () itp. Są dostępne na wszystkim i wszystkim.
Nawet Oracle zdało sobie sprawę ze znaczenia takiego podstawowego typu i planuje usunięcie prymitywów w Javie pod koniec 2017 roku
źródło
ToString()
iGetType()
będzie na każdym obiekcie. Prawdopodobnie warto zauważyć, że można użyć zmiennej typuobject
do przechowywania dowolnego obiektu .NET.