Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции по ая.docx
Скачиваний:
22
Добавлен:
20.09.2019
Размер:
276.66 Кб
Скачать

Полиморфизм

Рассмотрим семейство классов A1, A2, ... An, связанных отношением наследования. Класс Ak+1 является прямым потомком класса Ak. Пусть создана последовательность объектов x1, x2, ... xn, где xk - объект класса Ak. Пусть в классе A1 создан метод M с модификатором virtual, переопределяемый всеми потомками, так что в рамках семейства классов метод M существует в n формах, каждая из которых задает реализацию метода, выбранную соответствующим потомком. Рассмотрим основную операцию, инициирующую объектные вычисления - вызов объектом метода класса:

x1.M(arg1, arg2, … argN)

Контролем типов называется проверка каждого вызова, удостоверяющая, что:

  • в классе A1 объекта x1 действительно имеется метод M ;

  • у метода М действительно N формальных аргументов;

  • список фактических аргументов в точке вызова соответствует по числу и типам списку формальных аргументов метода M, заданного в классе A1.

Язык C#, как и большинство других языков программирования, позволяет выполнить эту проверку еще на этапе компиляции и в случае нарушений выдать сообщение об ошибке. Контроль типов, выполняемый на этапе компиляции, называется статическим контролем типов. Языки программирования, называемые динамическими, например, язык Smalltalk, производят этот контроль динамически - в ходе работы программы непосредственно перед выполнением метода. Ошибки, обнаруживаемые при динамическом контроле типов, трудно исправимы. У динамических языков есть свои преимущества - прежде всего, простота.

Перейдем к рассмотрению связывания. Снова рассмотрим основную операцию - вызов

x1.M(arg1, arg2, … argN);

Предположим, что статический контроль типов для этого вызова успешно выполнен. Рассмотрим еще один аспект, связанный с этим вызовом. x1 - это ссылка, которая связана с объектом, расположенным в динамической памяти. Тип объекта и тип ссылки могут не совпадать. Другими словами, объект, созданный в динамической памяти, может не принадлежать классу A1.

Предположим, что вызову метода M объектом x1 предшествует присваивание

x1 = y;

Это ссылочное присваивание, поскольку x1 - ссылка. Присваивание является допустимым, если объект y принадлежит классу, являющемуся потомком класса объекта x1. Таким образом, в точке вызова ссылка x1 может быть связана с объектом любого класса нашего семейства A1, …AN. В каждом из этих классов существует метод M с одной и той же сигнатурой. Метод M полиморфен: имея одно и то же имя и сигнатуру, он существует в разных формах - для каждого класса задана собственная реализация метода. Возникает естественный вопрос, какой же метод M следует вызывать - метод класса A1 (метод ссылки) или метод того класса, которому принадлежит реальный объект в динамической памяти (метод объекта). Возможны два решения этого вопроса, и оба варианта используются на практике.

Статическим связыванием называется связывание цели вызова и вызываемого метода на этапе компиляции, когда с сущностью связывается метод класса, заданного при объявлении сущности - метод ссылки.

Динамическим связыванием называется связывание цели вызова и вызываемого метода на этапе выполнения, когда с сущностью связывается метод класса объекта, связанного с сущностью в момент выполнения - метод объекта.

При статическом связывании метод выбирается из класса сущности, при динамическом - из класса объекта, связанного с сущностью. Понятно, что на этапе компиляции возможно только статическое связывание, поскольку только в период выполнения можно определить, с объектом какого класса связана данная сущность. Это может быть класс любого из потомков класса сущности.

Статическое связывание более эффективно в реализации, поскольку может быть сделано на этапе компиляции, так что при выполнении не потребуется никаких проверок. Динамическое связывание требует накладных расходов в период выполнения.

В языке C# принята следующая стратегия связывания. По умолчанию предполагается статическое связывание. Для того чтобы выполнялось динамическое связывание, родительский класс, впервые создающий метод, должен снабдить метод модификатором virtual или abstract, в классах потомках такой виртуальный метод будет иметь модификатор override.

Под полиморфизмом в ООП понимают способность одного и того же программного текста

x1.M(arg1, arg2, … argN);

выполняться по-разному, в зависимости от того, с каким объектом связана сущность x. Полиморфизм гарантирует, что вызываемый метод M будет принадлежать классу объекта, связанному с сущностью x. В основе полиморфизма, характерного для семейства классов, лежат три механизма.

  • Одностороннее присваивание объектов внутри семейства классов. Сущность, базовым классом которой является класс предка, можно связать с объектом любого из потомков. Другими словами, для введенной нами последовательности объектов xk присваивание xi = xj допустимо для всех j >=i.

  • Переопределение потомком метода, наследованного от родителя. Благодаря переопределению в семействе классов существует совокупность полиморфных методов с одинаковым именем и одинаковой сигнатурой, но разной реализацией.

  • Динамическое связывание, позволяющее в момент выполнения вызывать метод, принадлежащий объекту, с которым связана сущность в момент вызова.

В совокупности это и называется полиморфизмом семейства классов. Целевую сущность часто называют полиморфной сущностью, вызываемый метод - полиморфным методом, сам вызов - полиморфным вызовом.

Рассмотрим пример:

class Ferst

{

public virtual void P()

{

Console.WriteLine("Ferst");

}

}

class Second : Ferst

{

public override void P()

{

Console.WriteLine("Second");

}

}

class Third : Second

{

public override void P()

{

Console.WriteLine("Third");

}

}

class Program

{

static void Main()

{

Ferst member1 = new Ferst();

member1.P();

Second member2 = new Second();

member2.P();

Third member3 = new Third();

member3.P();

member2 = member3;

member2.P();

member1 = member3;

member1.P();

Console.ReadLine();

}

}

В приведенном примере создано три класса. Класс Ferst является родителем для класса Second, который в свою очередь является родителем для класса Third. В каждом классе определен метод P(). Причем в классе Ferst он указан как virtual. В оставшихся двух классах данный метод переопределен с использованием ключевого слова override. В зависимости от класса при выполнении метода P() на консоль выводиться различная информация.

В точке вызова программы создается по одному экземпляру для каждого из описанных классов. После создании каждого объекта выполняется вызов метода P(). Затем для демонстрации принципов полиморфизма объект member2 класса Second используется в качестве ссылки на объект member3 класса Third. При выполнении метода P() определенного на объекте member2 выполняется метод определенный в классе Third. Аналогично для объекта member1.