Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Выч. тех. - Лаба-3 - с изображением формы.docx
Скачиваний:
1
Добавлен:
12.11.2018
Размер:
704.61 Кб
Скачать

Добавление методов и изменение методов родителя

Потомок может создать новый собственный метод с именем, отличным от имен наследуемых методов. В этом случае никаких особенностей нет. Вот пример такого метода, создаваемого в классе Derived и возвращающего значение, которое хранится в скрытом поле credit:

//Методы

public string MyBaseCredit()

{

returnbase.credit.ToString();

}

Класс потомок может добавлять собственные методы, скрывать методы родителя. Но ситуация с методами более сложная, чем с полями, поскольку потомок может изменять реализацию родителя, задавая собственную реализацию метода. Если потомок создает метод с именем, совпадающим с именем метода предков, то возможны три ситуации:

  • перегрузка метода. Она возникает, когда сигнатура создаваемого метода отличается от сигнатуры наследуемых методов предков. В этом случае в классе потомка будет несколько перегруженных методов с одним именем, и вызов нужного метода определяется обычными правилами перегрузки методов;

  • переопределение метода. Метод родителя в этом случае должен иметь модификатор virtual, abstract или override. Это наиболее интересная ситуация, и она будет подробно рассмотрена. При переопределении сохраняется сигнатура и модификаторы доступа наследуемого метода;

  • скрытие метода. Если родительский метод не является виртуальным или абстрактным, то потомок может создать новый метод с тем же именем и той же сигнатурой, скрыв родительский метод в данном контексте. Здесь ситуация такая же, как и со скрытием полей. При вызове метода по его имени предпочтение будет отдаваться методу потомка. Это не означает, что метод родителя становится недоступным. Скрытый родительский метод всегда может быть вызван, если при вызове уточнить имя метода родительским именем base.

Метод потомка, скрывающий метод родителя, следует сопровождать модификатором new, указывающим на новый метод. Если этот модификатор опущен, но из контекста ясно, что речь идет о новом методе, то выдается предупреждающее сообщение при компиляции проекта.

Вернемся к нашему примеру. Класс Found имел в своем составе метод Parse. Его потомок класс Derived расширил возможности метода, добавив проверку в метод разбора. Поскольку родительский метод Parse не был ни виртуальным, ни абстрактным, то новый метод Parse, добавленный потомком, скрывает родительский метод:

new public string Parse()

{

string res = base.Parse() + NL;

res += "Выполнена проверка кода!";

returnres;

}

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

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

Статический контроль типов и динамическое связывание

Рассмотрим семейство классов 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 (метод ссылки) или метод того класса, которому принадлежит реальный объект в динамической памяти (метод объекта). Возможны два решения этого вопроса, и оба варианта используются на практике.

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

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

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

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

Уже достаточно давно разработан эффективный механизм реализации динамического связывания. Еще на этапе компиляции подготавливается так называемая таблица виртуальных методов, содержащая их адреса. Связывание объекта xk с принадлежащим ему методом Mk производится выбором соответствующего элемента из этой таблицы и выполняется не сложнее, чем получение по индексу соответствующего элемента массива.

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