W Alan Kays Definition Object Oriented znajduje się definicja, której częściowo nie rozumiem:
OOP oznacza dla mnie tylko wysyłanie wiadomości, lokalne przechowywanie, ochronę i ukrywanie procesów państwowych oraz ekstremalne późne wiązanie wszystkich rzeczy.
Ale co oznacza „LateBinding”? Jak mogę to zastosować w języku takim jak C #? I dlaczego to takie ważne?
object-oriented
object-oriented-design
Luca Zulian
źródło
źródło
Odpowiedzi:
„Powiązanie” odnosi się do rozstrzygnięcia nazwy metody na fragment kodu możliwego do wywołania. Zwykle wywołanie funkcji można rozwiązać w czasie kompilacji lub w czasie łącza. Przykładem języka używającego wiązania statycznego jest C:
W tym
foo(40)
przypadku kompilator może rozwiązać połączenie. To wczesne pozwala na pewne optymalizacje, takie jak wstawianie. Najważniejsze zalety to:Z drugiej strony niektóre języki odraczają rozstrzyganie funkcji do ostatniej możliwej chwili. Przykładem jest Python, w którym możemy na nowo zdefiniować symbole w locie:
To jest przykład późnego wiązania. Chociaż sprawia, że rygorystyczne sprawdzanie typów jest nieuzasadnione (sprawdzanie typów może być wykonywane tylko w czasie wykonywania), jest jednak znacznie bardziej elastyczne i pozwala nam wyrażać pojęcia, których nie można wyrazić w ramach statycznego pisania lub wczesnego wiązania. Na przykład możemy dodać nowe funkcje w czasie wykonywania.
Wysyłanie metod, tak jak jest to powszechnie stosowane w „statycznych” językach OOP, znajduje się gdzieś pomiędzy tymi dwoma skrajnościami: Klasa z góry deklaruje rodzaj wszystkich obsługiwanych operacji, więc są one statycznie znane i można je sprawdzić. Następnie możemy zbudować prostą tablicę przeglądową (VTable), która wskazuje na faktyczną implementację. Każdy obiekt zawiera wskaźnik do vtable. System typów gwarantuje, że każdy otrzymany obiekt będzie miał odpowiednią tabelę vtable, ale nie mamy pojęcia w czasie kompilacji, jaka jest wartość tej tabeli odnośników. Dlatego obiektów można używać do przekazywania funkcji jako danych (połowa powodów, dla których OOP i programowanie funkcji są równoważne). Tabele Vtab można łatwo zaimplementować w dowolnym języku obsługującym wskaźniki funkcji, takim jak C.
Ten rodzaj wyszukiwania metody jest również znany jako „dynamiczna wysyłka” i gdzieś pomiędzy wczesnym wiązaniem a późnym wiązaniem. Uważam dynamiczne wysyłanie metod za centralną właściwość definiującą programowanie OOP, a wszystko inne (np. Enkapsulacja, subtyping,…) jest drugorzędne. Pozwala nam wprowadzić polimorfizm do naszego kodu, a nawet dodać nowe zachowanie do fragmentu kodu bez konieczności jego ponownej kompilacji! W przykładzie C każdy może dodać nowy vtable i przekazać obiekt z tym vtable do
sayHelloToMeredith()
.Chociaż jest to późne wiązanie, nie jest to „ekstremalne późne wiązanie”, za którym opowiada się Kay. Zamiast modelu koncepcyjnego „wysyłanie metod za pomocą wskaźników funkcji” stosuje „wysyłanie metod poprzez przekazywanie komunikatów”. Jest to ważne rozróżnienie, ponieważ przekazywanie wiadomości jest znacznie bardziej ogólne. W tym modelu każdy obiekt ma skrzynkę odbiorczą, w której inne obiekty mogą umieszczać wiadomości. Obiekt odbierający może następnie spróbować zinterpretować tę wiadomość. Najbardziej znanym systemem OOP jest WWW. Tutaj wiadomości są żądaniami HTTP, a serwery są obiektami.
Na przykład mogę zapytać serwer programmers.stackexchange.se
GET /questions/301919/
. Porównaj to z notacjąprogrammers.get("/questions/301919/")
. Serwer może odrzucić tę prośbę lub odesłać mi błąd lub może dostarczyć mi twoje pytanie.Moc przekazywania wiadomości polega na tym, że skaluje się bardzo dobrze: żadne dane nie są udostępniane (tylko przesyłane), wszystko może się odbywać asynchronicznie, a obiekty mogą interpretować wiadomości w dowolny sposób. To sprawia, że przekazujący komunikat system OOP jest łatwo rozszerzalny. Mogę wysyłać wiadomości, które nie wszyscy rozumieją, albo odzyskać oczekiwany wynik lub błąd. Obiekt nie musi deklarować z góry, na które komunikaty odpowie.
To nakłada odpowiedzialność za utrzymanie poprawności na odbiorcę wiadomości, myśl zwaną także enkapsulacją. Np. Nie mogę odczytać pliku z serwera HTTP bez pytania go za pomocą wiadomości HTTP. Dzięki temu serwer HTTP może odrzucić moje żądanie, np. Jeśli nie mam uprawnień. W mniejszej skali OOP oznacza to, że nie mam dostępu do odczytu i zapisu do wewnętrznego stanu obiektu, ale muszę przejść przez metody publiczne. Serwer HTTP też nie musi mi podawać pliku. Może to być dynamicznie generowana zawartość z bazy danych. W rzeczywistym OOP mechanizm reakcji obiektu na komunikaty może zostać wyłączony bez zauważenia przez użytkownika. Jest to silniejsze niż „odbicie”, ale zwykle pełny protokół meta-obiektów. Mój powyższy przykład C nie może zmienić mechanizmu wysyłania w czasie wykonywania.
Możliwość zmiany mechanizmu wysyłania oznacza późne wiązanie, ponieważ wszystkie wiadomości są kierowane za pomocą kodu definiowanego przez użytkownika. Jest to niezwykle potężne: biorąc pod uwagę protokół metaobiektu, mogę dodawać funkcje, takie jak klasy, prototypy, dziedziczenie, klasy abstrakcyjne, interfejsy, cechy, wielokrotne dziedziczenie, wielozadaniowość, programowanie aspektowe, odbicie, zdalne wywoływanie metod, obiekty proxy itp. dla języka, który nie zaczyna się od tych funkcji. Ta moc ewolucji jest całkowicie nieobecna w bardziej statycznych językach, takich jak C #, Java lub C ++.
źródło
Późne wiązanie odnosi się do tego, jak obiekty komunikują się ze sobą. Ideałem, który Alan stara się osiągnąć, jest możliwie luźne sprzężenie przedmiotów. Innymi słowy, że obiekt musi znać minimum, aby komunikować się z innym obiektem.
Dlaczego? Ponieważ zachęca to do samodzielnej zmiany części systemu i umożliwia mu wzrost i zmianę organiczną.
Na przykład w języku C # możesz napisać w metodzie dla
obj1
czegoś takiegoobj2.doSomething()
. Możesz traktować to jakoobj1
komunikację zobj2
. Aby tak się stało w języku C #,obj1
trzeba się trochę dowiedziećobj2
. Będzie musiał znać swoją klasę. Sprawdziłby, czy klasa ma wywoływaną metodędoSomething
i czy istnieje wersja tej metody, która przyjmuje zerowe parametry.Teraz wyobraź sobie system, w którym wysyłasz wiadomość przez sieć lub podobny. możesz napisać coś takiego
Runtime.sendMsg(ipAddress, "doSomething")
. W takim przypadku nie musisz dużo wiedzieć o maszynie, z którą się komunikujesz; prawdopodobnie można się z nim skontaktować przez IP i zrobi coś, gdy otrzyma ciąg „doSomething”. Ale poza tym wiesz bardzo mało.Teraz wyobraź sobie, że tak komunikują się przedmioty. Znasz adres i możesz wysyłać dowolne wiadomości na ten adres za pomocą funkcji „skrzynki pocztowej”. W tym przypadku
obj1
nie trzeba wiele wiedziećobj2
, wystarczy adres. Nie musi nawet wiedzieć, że to rozumiedoSomething
.To właściwie sedno późnego wiązania. Teraz w językach, które go używają, takich jak Smalltalk i Objective-C, zwykle jest trochę cukru syntaktycznego, aby ukryć funkcję skrzynki pocztowej. Ale poza tym pomysł jest taki sam.
W języku C # można go replikować, w pewnym sensie, mając
Runtime
klasę, która akceptuje ref obiektu i ciąg znaków i używa refleksji do znalezienia metody i wywołania jej (zacznie się komplikować z argumentami i zwracanymi wartościami, ale byłoby to możliwe brzydki).Edycja: aby uniknąć zamieszania w odniesieniu do znaczenia późnego wiązania. W tej odpowiedzi mam na myśli późne wiązanie, ponieważ rozumiem, że Alan Kay miał na myśli to i zaimplementował je w Smalltalk. Nie jest to bardziej powszechne, nowoczesne użycie terminu, który ogólnie odnosi się do wysyłki dynamicznej. Ten ostatni obejmuje opóźnienie w rozwiązaniu dokładnej metody do czasu uruchomienia, ale nadal wymaga pewnych informacji o typie dla odbiornika w czasie kompilacji.
źródło