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