Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Lektsii_po_programmirovaniyu_3_semestr.doc
Скачиваний:
1
Добавлен:
01.05.2025
Размер:
2.11 Mб
Скачать

Полиморфизм

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

* Приведение объекта класса-потомка к объектам родительских классов. Этот механизм позволяет рассматривать объекты классов-потомков и как полноценные представители их родительских классов.

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

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

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

Полиморфизм наследующих классов.

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

Допустим, что мы создали три класса, связанных между собой отношением наследования и описывающих такие понятия как транспортное средство (корневой класс ТС), наземное транспортное средство (класс НТС) и мотоцикл, как разновидность наземного транспортного средства (класс М). Иерархическое дерево наследования для этих классов представить как: ТС НТС М.

С помощью оператора new можно создать экземпляр любого класса, который входит в это дерево наследования. Например, экземпляр упомянутых классов можно создать с помощью следующей записи на языке С#:

class TC {…}

class НTC : TC {…}

class M : HTC {…}

TC vehicle = new TC();

HTC groundVehicle = new HTC();

M motocycle = new M();

В результате, под экземпляры указанных классов компьютером будут выделены соответствующие области памяти, размеры которых соответствуют их типам. А переменные vehicle, motocycle и motocycle будут хранить соответствующие адреса области памяти, выделенной под эти объекты. Допустим, что значение адреса под объект типа мотоцикла, равно 1000, а объем памяти, выделяемый под каждый тип объектов условно примем равными соответственно: 50, 86 и 100 байтам.

Анализируя структуру содержимого области памяти, выделенной под объект типа мотоцикла, можно обнаружить следующую особенность. Вначале эта область заполняется элементами корневого базового класса ТС, после чего – элементами базового класса НТС и только после этого оставшиеся ячейки памяти заполняются элементами, объявленными в классе последнего потомка (мотоцикла). Именно в такой последовательности и осуществляется выделение памяти под экземпляр любого класса, являющегося потомком других классов. В том же порядке вызываются и конструкторы его базовых классов. А это, в свою очередь, означает, что в выделенной области памяти содержатся не один, а три объекта: экземпляр класса мотоцикла, экземпляр класса наземного транспортного средства и экземпляр класса транспортного средства. Но это так и должно быть потому, что мотоцикл действительно является частным случаем понятия наземного транспортного. В свою очередь, наземное транспортное средство тоже является частным случаем еще более общего понятия – транспортное средство. Отсюда легко придти к заключению о том, что, при необходимости, из экземпляра класса-потомка можно всегда выделить ту его часть, которая полностью копирует все элементы ее базового класса.

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

  • если приводимые типы (классы) связаны между собой отношением наследования;

  • если класс, к типу которого нужно привести (преобразовать) объект, является родительским классом.

Первое ограничение объясняется тем, что из объекта невозможно выделить объект другого класса, если в нем нет элементов этого класса. А это возможно только в том случае, если классы приводимых объектов находятся в отношении наследования.

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

На языке C# это можно показать с помощью следующего примера:

class A{…}

class B : A{…}

A a = new A();

B b = new B();

a = (A)b; // Такое преобразование типов сделать можно.

в = (B)a; // А вот такое – нельзя. Это ошибка!

Возможность приведения объектов, связанных отношением наследования, к типу их родителя обусловлена тем, что в составе класса-потомка (переменная b класса В) содержатся все элементы его родительского класса (класса А). То есть, в классе потомка есть вся информация о его родительских классах. А вот приведение экземпляра родительского класса (например, переменной а класса А) к типу класса, который является его потомком (например, переменной b класса В), уже невозможно потому, что в объекте родительского класса (переменной а класса А) не содержится ни одного элемента, принадлежащих его потомкам (класса В). Другими словами, родительский класс не располагает никакой информацией о своих потомках.

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

Правила приведения типов.

Если имеются объекты, состоящие в отношении наследования, то можно запомнить и обращаться к каждому из них по ссылке на его базовый класс. Первым правилом преобразования типов классов является то, что когда два класса находятся в отношении наследования, всегда можно сохранить производный тип в ссылке базового класса. Формально это называют неявным приведением типов, поскольку оно работает только в рамках законов наследования. Такой подход позволяет строить очень мощные программные конструкции.

Язык С# обеспечивает три способа определения того, что ссылка на объект базового класса действительно указывает на объект производного типа: явное приведение типа, ключевое слово is и ключевое слово as.

Ключевое слово is возвращает логическое значение, указывающее на совместимость ссылки на объект базового класса со ссылкой на объект производного класса. Например:

class A

{

functionА(){…}// функция базового класса А

}

class B : A

{

functionВ(){…}// собственная функция в классе В

}

public void f( A obj)

{

if(obj is B)

{

(В)obj.functionB();// вызов функции класса В

}

}

Здесь ключевое слово is используется для того, чтобы динамически (т.е. во время работы программы) определить реальный тип объекта. Однако, для того, чтобы получить доступ к содержимому реального объекта, необходимо использовать явное приведение типов.

Альтернативным способом является использование ключевого слова as для получения ссылки на объект производного типа. Если типы окажутся несовместимыми (например, не состоящими в отношении наследования), то ссылка получит значение равное null:

A a = new B();

B obj = a as B;

if(obj!=null) { obj.functionВ(); }// вызов функции класса В

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]