Jednym z celów projektowania frameworków, takich jak Java i .NET, jest umożliwienie skompilowanemu kodowi do pracy z jedną wersją wstępnie skompilowanej biblioteki, tak samo dobrze współpracuje z kolejnymi wersjami tej biblioteki, nawet jeśli te kolejne wersje dodać nowe funkcje. Podczas gdy normalnym paradygmatem w językach takich jak C lub C ++ jest dystrybucja połączonych statycznie plików wykonywalnych, które zawierają wszystkie potrzebne im biblioteki, paradygmatem w .NET i Javie jest dystrybucja aplikacji jako kolekcji komponentów, które są „połączone” w czasie wykonywania .
Model COM, który poprzedzał .NET, próbował zastosować to ogólne podejście, ale tak naprawdę nie miał dziedziczenia - zamiast tego każda definicja klasy skutecznie definiowała zarówno klasę, jak i interfejs o tej samej nazwie, który zawierał wszystkich publicznych członków. Instancje były typu klasowego, podczas gdy odwołania były typu interfejsu. Zadeklarowanie klasy jako pochodnej od innej było równoznaczne z zadeklarowaniem klasy jako implementującej interfejs drugiej osoby i wymagało, aby nowa klasa ponownie zaimplementowała wszystkie publiczne elementy członkowskie klas, z których się wywodzi. Jeśli Y i Z wywodzą się z X, a W wywodzi się z Y i Z, nie będzie miało znaczenia, czy Y i Z implementują elementy X inaczej, ponieważ Z nie będzie w stanie użyć ich implementacji - będzie musiał zdefiniować swoje posiadać. W może zawierać wystąpienia Y i / lub Z,
Trudność w Javie i .NET polega na tym, że kod może dziedziczyć elementy członkowskie i mieć do nich dostęp niejawnie odwołując się do elementów nadrzędnych. Załóżmy, że mamy klasy WZ powiązane jak wyżej:
class X { public virtual void Foo() { Console.WriteLine("XFoo"); }
class Y : X {};
class Z : X {};
class W : Y, Z // Not actually permitted in C#
{
public static void Test()
{
var it = new W();
it.Foo();
}
}
Wydawałoby się, że W.Test()
utworzenie instancji W powinno wywołać implementację metody wirtualnej Foo
zdefiniowanej w X
. Załóżmy jednak, że Y i Z faktycznie znajdowały się w osobno skompilowanym module i chociaż zostały zdefiniowane jak powyżej, gdy kompilowano X i W, zostały później zmienione i ponownie skompilowane:
class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }
Jaki powinien być efekt wezwania W.Test()
? Gdyby program musiał być statycznie połączony przed dystrybucją, etap łącza statycznego mógłby być w stanie stwierdzić, że chociaż program nie miał dwuznaczności przed zmianą Y i Z, zmiany w Y i Z sprawiły, że rzeczy stały się niejednoznaczne i konsolidator mógłby odmówić skompilować program, chyba że lub do czasu rozwiązania takiej niejednoznaczności. Z drugiej strony możliwe jest, że osoba, która ma zarówno W, jak i nowe wersje Y i Z, jest kimś, kto po prostu chce uruchomić program i nie ma kodu źródłowego żadnego z nich. Kiedy W.Test()
biegnie, nie będzie już jasne, coW.Test()
powinien zrobić, ale dopóki użytkownik nie spróbuje uruchomić W z nową wersją Y i Z, żadna część systemu nie będzie w stanie rozpoznać problemu (chyba że W został uznany za nielegalny nawet przed zmianami na Y i Z) .