Одна из целей разработки таких фреймворков, как 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 вызовет реализацию виртуального метода, 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) ,