Co wiąże wcześnie i późno?


77

Ciągle słyszę o wczesnym i późnym wiązaniu, ale nie rozumiem, czym one są. Znalazłem następujące wyjaśnienie, którego nie rozumiem:

Wczesne wiązanie odnosi się do przypisywania wartości do zmiennych w czasie projektowania, natomiast późne wiązanie odnosi się do przypisywania wartości do zmiennych w czasie wykonywania.

Czy ktoś mógłby zdefiniować dwa rodzaje opraw i porównać je?


1
czas kompilacji a środowisko wykonawcze.
barlop

Odpowiedzi:


84

Pomieszane są dwie główne koncepcje: wiązanie i ładowanie. Jest to powiązane z koncepcją DataBinding, która gdzieś w środku często robi obie te rzeczy. Po zastanowieniu się, dodam jeszcze jedną koncepcję, aby ukończyć trifectę, wysłać.

Rodzaje

Późne wiązanie : typ jest nieznany, dopóki zmienna nie zostanie wykonana w czasie wykonywania; zwykle poprzez przypisanie, ale istnieją inne sposoby na przymus typu; języki dynamicznie typowane nazywają to podstawową cechą, ale wiele języków typowanych statycznie ma pewną metodę osiągnięcia późnego wiązania

Wdrażane często przy użyciu [specjalnych] typów dynamicznych, introspekcji / refleksji, flag i opcji kompilatora lub metodami wirtualnymi poprzez pożyczanie i rozszerzanie dynamicznej wysyłki

Wczesne wiązanie : typ jest znany, zanim zmienna zostanie wykorzystana w czasie wykonywania, zwykle za pomocą statycznych, deklaratywnych środków

Wdrażane często przy użyciu standardowych typów pierwotnych

Funkcje

Wysyłka statyczna : znana, określona funkcja lub podprogram w czasie kompilacji; jest jednoznaczny i zgodny z podpisem

Zaimplementowane jako funkcje statyczne; żadna metoda nie może mieć tego samego podpisu

Dynamiczna wysyłka : nie jest to konkretna funkcja ani podprogram w czasie kompilacji; określone przez kontekst podczas wykonywania. Istnieją dwa różne podejścia do „dynamicznej wysyłki”, w zależności od tego, jakie informacje kontekstowe są wykorzystywane do wyboru odpowiedniej implementacji funkcji.

W przypadku pojedynczej [ dynamicznej ] wysyłki tylko typ instancji jest używany do określenia odpowiedniej implementacji funkcji. W językach o typie statycznym oznacza to w praktyce, że typ instancji decyduje, która implementacja metody zostanie zastosowana, niezależnie od typu odniesienia wskazanego, gdy zmienna jest deklarowana / przypisywana. Ponieważ tylko jeden typ - typ instancji obiektu - służy do wnioskowania o odpowiedniej implementacji, podejście to nazywa się „pojedynczą wysyłką”.

Istnieje również wiele [ dynamicznych ] wysyłek , w których typy parametrów wejściowych pomagają również określić, która implementacja funkcji ma zostać wywołana. Ponieważ wiele typów - zarówno typ wystąpienia, jak i typ (parametry) parametru - wpływają na wybraną implementację metody, podejście to nosi nazwę „wielokrotnego wysyłania”.

Zaimplementowane jako funkcje wirtualne lub abstrakcyjne; inne wskazówki obejmują przesłonięte, ukryte lub zaciemnione metody.

Uwaga: To, czy przeciążenie metody wymaga dynamicznej wysyłki, zależy od języka. Na przykład w Javie przeciążone metody są wysyłane statycznie.

Wartości

Leniwe ładowanie : strategia inicjowania obiektu, która odkłada przypisanie wartości, dopóki nie będzie potrzebne ; pozwala, aby obiekt był zasadniczo poprawny, ale świadomie niekompletny i czekał, aż dane będą potrzebne przed załadowaniem; często uważane za szczególnie przydatne do ładowania dużych zestawów danych lub oczekiwania na zasoby zewnętrzne

Implementowane często przez celowe nie ładowanie kolekcji lub listy do obiektu złożonego podczas wywołań konstruktora lub inicjalizacji, dopóki jakiś dalszy program wywołujący nie poprosi o wyświetlenie zawartości tej kolekcji (np. Get_value_at, get_all_as itp.). Odmiany obejmują ładowanie meta informacji o kolekcji (takich jak rozmiar lub klucze), ale pomijanie rzeczywistych danych; zapewnia również mechanizm dla niektórych środowisk wykonawczych, aby zapewnić programistom dość bezpieczny i wydajny schemat wdrażania singletonów

Chętne ładowanie : strategia inicjowania obiektu, która natychmiast wykonuje wszystkie przypisania wartości , aby mieć wszystkie dane potrzebne do uzupełnienia, zanim uzna się za prawidłowy.

Implementowane często przez dostarczenie obiektom złożonym wszystkich znanych danych tak szybko, jak to możliwe, na przykład podczas wywołania konstruktora lub inicjalizacji

Wiązanie danych : często wiąże się z tworzeniem aktywnego łącza lub mapy między dwoma kompatybilnymi strumieniami informacji , aby zmiany w jednym były odzwierciedlane z powrotem w drugim i odwrotnie; w celu zachowania zgodności często muszą mieć wspólny typ podstawowy lub interfejs

Często wdrażany jako próba zapewnienia czystszej, spójnej synchronizacji między różnymi aspektami aplikacji (np. Widok-model do wyświetlenia, model do kontrolera itp.) I mówi o pojęciach takich jak źródło i cel, punkty końcowe, powiązanie / rozpięcie, aktualizacja i zdarzenia, takie jak on_bind, on_property_change, on_explicit, on_out_of_scope


EDYTUJ UWAGĘ: Ostatnia ważna edycja, która zawiera opis przykładów tego, jak często się to zdarza. Poszczególne przykłady kodu zależą całkowicie od implementacji / środowiska wykonawczego / platformy


2
Ta odpowiedź wydaje się zbyt specyficzna dla języków obiektowych.
Jack

27

Wszystko, co kompilator decyduje podczas kompilacji, może odnosić się do wiązania EARLY / COMPILE TIME, a wszystko, co ma zostać określone w RUNTIME, nazywa się wiązaniem LATE / RUNTIME .

Na przykład,

Przeciążanie metod i zastępowanie metod .

1) W metodzie Przeciążenie twoje wywołania metod do metod są ustalane przez kompilator w tym sensie, że która funkcja ma zostać wywołana, decyduje kompilator w czasie kompilacji. Stąd bycie WCZESNYM WIĄZANIEM .

2) W metodzie Zastępowanie decyduje się w RUNTIME, która metoda zostanie wywołana. Jest to określane jako PÓŹNE WIĄZANIE .

Starałem się, aby było to proste i łatwe do zdobycia. Mam nadzieję że to pomoże.


9

Późne wiązanie ma miejsce, gdy zachowanie jest oceniane w czasie wykonywania. Jest to konieczne, gdy naprawdę chcesz określić sposób działania na podstawie informacji, które posiadasz tylko podczas działania programu. Moim zdaniem najczystszym przykładem jest mechanizm funkcji wirtualnej, szczególnie w C ++.

class A
{
public:
    void f() {}
    virtual void g() {}
};

class B : public A
{
    void f() {}
    virtual void g() {}
};

int main()
{
    A* a = new B;
    a->f();
    a->g();
}

W tym przykładzie a->f()faktycznie zadzwoni void A::f(), ponieważ jest wcześnie (lub statycznie) związany, a więc program w czasie wykonywania myśli, że jest tylko wskaźnikiem do Azmiennej typu, podczas gdy a->g()faktycznie zadzwoni void B::g(), ponieważ kompilator, widząc, g()jest wirtualny, wstrzykuje kod, aby szukać w górę adres poprawnej funkcji do wywołania w czasie wykonywania.


1
„Środowisko wykonawcze”? Mówisz o C ++. C ++ kompiluje bezpośrednio do kodu maszynowego, nie potrzebuje środowiska wykonawczego do rozpoznawania metod wirtualnych.
tdammers

3
@tdammers C ++ faktycznie potrzebuje biblioteki wykonawczej, choć nie do wywołań wirtualnych. Jeśli przeczytasz uważnie, zauważysz, że ta odpowiedź mówi, że kompilator „wstrzykuje kod, aby wyszukać adres poprawnej funkcji [...] w czasie wykonywania”.

No cóż, ale ten „kod do wyszukania adresu poprawnej funkcji” jest w zasadzie tylko dwuetapowym dereferencją wskaźnika zależnym od typu, po którym następuje wywołanie funkcji. Nie ma w tym „myślenia”; jedynym powodem, dla którego działa niezawodnie, jest to, że kompilator sprawdza typ w czasie kompilacji ; w czasie wykonywania wygenerowany kod ufa kompilatorowi, że wykonał zadanie domowe sprawdzania typu. Jeśli używasz niebezpiecznych odlewane (np wskaźnik C-styl rzuca), użytkownik może legalnie traktować C ++ obiektów jako przedmiotu niewłaściwej klasie, ale ich vtables zostanie całkowicie zawiedli i kod po prostu łamie.
tdammers

@tdammers Próbowałem trzymać się z dala od tego rodzaju odpowiedzi, ponieważ jest to szczegół implementacji kompilatorów, który może, ale nie musi być prawdziwy, dla niektórych ezoterycznych kompilatorów. Liczy się koncepcja.
Yam Marcovic

1
@tdammers A przez „środowisko uruchomieniowe” rozumiem „program w środowisku uruchomieniowym”. Oczywiście C ++ nie jest zarządzany. Ale ponieważ pokazałeś mi, że może to powodować zamieszanie, zmieniam to na pełne sformułowanie.
Yam Marcovic

5

jeśli znasz wskaźniki funkcji, byłby to przykład. Zdefiniowane funkcje można nazwać wczesnymi. natomiast jeśli użyjesz wskaźników funkcji, jego późne wiązanie.

  int add(int x,int y)
  {
    return x+y;
  }
  int sub(int x,int y)
  {
      return x-y;
  }


    int main()
    {
     //get user choice
     int(*fp)(int,int);
     //if add
      fp=add;
     //else if sub
     fp=sub;
     cout<<fp(2,2);
    }

tutaj funkcje add i sub są funkcjami (jego adres jest powiązany w kompilatorze czasowym)

ale wskaźnik funkcji jest późno powiązany, fp może wywołać add lub sub w zależności od wyboru użytkownika [w czasie wykonywania].


3

Wczesne i późne wiązanie ma sens tylko w kontekście typów, a nie sposobu, w jaki je opisujesz. Prawie wszystkie współczesne języki są pisane w tym sensie, że wszystkie wartości mają ustalone typy. Różnica pojawia się, gdy patrzymy na języki dynamicznie vs. statycznie typowane. W dynamicznie typowanych językach zmienne nie mają typów, więc mogą odnosić się do wartości dowolnego typu, a to oznacza, że ​​kiedy wywołujesz metodę na obiekcie, do którego odwołuje się jakaś zmienna, jedynym sposobem ustalenia, czy to wywołanie jest poprawne, jest sprawdź klasę obiektu i sprawdź, czy ta metoda rzeczywiście istnieje. Pozwala to na kilka fajnych rzeczy, takich jak dodawanie nowych metod do klas w czasie wykonywania, ponieważ faktyczne wyszukiwanie metod jest odraczane do ostatniej chwili. Większość ludzi nazywa ten stan rzeczy późnym wiązaniem.

W języku statycznie typowanym zmienne mają typy i po zadeklarowaniu nie mogą odnosić się do żadnej wartości, która nie jest tego samego typu. To nie jest do końca prawda, ale załóżmy na razie. Teraz, jeśli wiesz, że zmienna zawsze będzie odnosić się tylko do wartości określonego typu, nie ma powodu, aby dowiedzieć się, czy wywołanie metody jest poprawne, czy nie w czasie wykonywania, ponieważ możesz określić poprawność przed uruchomieniem kodu. Jest to określane jako wczesne wiązanie.

Przykład wykazujący późne wiązanie w rubinie:

a = 1 # a is an integer at this point
a.succ # asking for its successor is valid

class A
  def method_a
    # some code
  end
end

a = A.new
a.method_a # this is also valid
a.succ # this is not valid


class A # we can re-open the class and add a method
  def succ
    # some more code
  end
end
a.succ # now this is valid

Powyższa sekwencja działań nie jest możliwa w języku takim jak Java, w którym wszystkie typy są naprawiane w czasie wykonywania.


1

Zamiast podania akademickiej definicji postaram się pokazać niektóre różnice na przykładzie z prawdziwego świata przy użyciu VBA:

Wczesne wiązanie:

Dim x As FileSystemObject
Set x = New FileSystemObject
Debug.Print x.GetSpecialFolder(0)

Wymaga to ustawienia odniesienia do komponentu „Microsoft Scripting Runtime” w czasie projektowania . Ma tę zaletę, że pojawia się komunikat o błędzie już w czasie kompilacji, gdy masz literówkę FileSystemObjectlub nazwę metody, taką jak GetSpecialFolder.

Późne wiązanie

Dim x As Object
Set x = CreateObject("Scripting.FileSystemObject")
Debug.Print x.GetSpecialFolder(0)

Nie wymaga to wcześniejszego ustawienia odniesienia, tworzenie instancji i określanie typu nastąpi po prostu w czasie wykonywania. Kompilator nie będzie narzekał w czasie kompilacji, gdy spróbujesz wywołać nieistniejącą metodę x, co doprowadzi do błędu w czasie wykonywania tylko po wykonaniu określonego wiersza.

Wadą późnego wiązania jest to, że nie ma tutaj żadnego silnego sprawdzania typu. Ale to także zaleta - powiedzmy, że masz komponent, w którym istnieje kilka wersji, a każda nowsza wersja zapewnia dodatkowe funkcje. (Przykładem w świecie rzeczywistym są komponenty MS Office, takie jak interfejs Excel COM) Późne wiązanie pozwala napisać kod, który współpracuje ze wszystkimi tymi wersjami - możesz najpierw określić konkretną wersję komponentu, a jeśli okaże się, że masz dostępna jest tylko starsza wersja, unikaj wykonywania wywołań funkcji, które nie działają z tą wersją.


-2

Być może najczęstszym przykładem późnego wiązania jest rozwiązywanie internetowych adresów URL. Obsługuje dynamiczne systemy i duże systemy, nie próbując łączyć i wiązać wszystkich witryn na świecie, zanim będzie można uzyskać do nich dostęp, ale z drugiej strony wiąże się to z pewnym nakładem pracy (wyszukiwanie DNS, znacznie mniej routingu IP) w czasie wykonywania.

W tym świetle większość odmian wiązania w środowiskach językowych jest mniej więcej wcześnie, w czasie kompilacji lub łącza.

Każdy rodzaj ma koszty i korzyści.


Czy umiesz znaleźć odniesienie do tej definicji wiązania? Nie słyszałem o rozwiązywaniu adresów internetowych jako „wiążących”, chociaż ponieważ wiązanie jest aktem rozwiązywania nazw, przypuszczam, że ktoś argumentował, że pojęcie wczesnego / późnego wiązania można zastosować do rozwiązywania URI adresów internetowych. Ale nie jest to powszechna interpretacja, a koncepcja wczesnego / późnego wiązania poprzedza czas, w którym komputery były powszechnie podłączone do Internetu.
Jay Elston,
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.