Однією з цілей дизайну рамок, таких як Java та .NET, є можливість коду, який складається, працювати з однією версією попередньо складеної бібліотеки, однаково добре працювати з наступними версіями цієї бібліотеки, навіть якщо ці наступні версії додати нові функції. У той час як звичайна парадигма на таких мовах, як C або C ++, полягає в поширенні статично пов'язаних виконуваних файлів, що містять усі необхідні їм бібліотеки, парадигма в .NET і Java полягає в розповсюдженні програм у вигляді колекцій компонентів, які "пов'язані" під час виконання. .
Модель COM, що передувала .NET, намагалася використовувати цей загальний підхід, але насправді наслідування не мало - натомість кожне визначення класу ефективно визначало як клас, так і інтерфейс з тим же ім'ям, який містив усіх його публічних членів. Екземпляри були типу класу, тоді як посилання були типу інтерфейсу. Оголошено клас таким, що походить від іншого, було рівнозначним оголошенню класу як інтерфейсу іншого та вимагав від нового класу повторної реалізації всіх публічних членів класів, з яких похідний. Якщо Y і Z походять від X, а тоді W походить від Y і Z, не має значення, якщо Y і Z по-різному реалізують члени X, тому що Z не зможе використовувати їх реалізацію - доведеться визначити його власний. W може інкапсулювати екземпляри Y та / або Z,
Складність у Java та .NET полягає в тому, що коду дозволено успадковувати членів і мати доступ до них неявно посилається на батьківські члени. Припустимо, один з класів WZ, як описано вище:
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();
}
}
Здавалося б, W.Test()
слід створити екземпляр W call реалізації віртуального методу, Foo
визначеного в X
. Припустимо, однак, що Y і Z насправді були окремо складеним модулем, і хоча вони були визначені як вище, коли компілюються X і W, вони пізніше були змінені і перекомпільовані:
class Y : X { public override void Foo() { Console.WriteLine("YFoo"); }
class Z : X { public override void Foo() { Console.WriteLine("ZFoo"); }
Тепер яким повинен бути ефект дзвінка W.Test()
? Якщо програму довелося статично зв’язати перед розповсюдженням, етап статичного зв’язку може виявити, що, хоча програма не мала двозначності перед зміною Y і Z, зміни Y і Z зробили речі неоднозначними, і лінкер міг відмовитись будувати програму, доки не буде вирішена така двозначність. З іншого боку, можливо, що людина, яка має і W, і нові версії Y і Z, - це хтось, хто просто хоче запустити програму і не має вихідного коду для жодної з неї. Коли W.Test()
працює, то вже не буде зрозуміло, щоW.Test()
слід робити, але поки користувач не спробував запустити W з новою версією Y і Z, жодна частина системи не змогла б визнати, що виникає проблема (якщо тільки W не вважалася нелегітимною ще до зміни Y і Z ).