
- •Программа курса
- •Ооп. Основные понятия
- •Конструкторы, деструкторы и др. Работа с объектами в .Net
- •Ссылочные типы и Типы значения. Передача параметров в .Net
- •Принципы ооп
- •Инкапсуляция
- •Наследование
- •Полиморфизм
- •Важные классы (типы данных)
- •Обобщенные контейнеры
- •Статические методы и свойства
- •Обработка исключений
- •Интерфейсы и слабая связь между классами
- •Рефлексия и создание объектов из внешних модулей по имени класса
Полиморфизм
Полиморфизм означает ‘один интерфейс – много реализаций’. Известный пример полиморфизма – операции сложения для текстовых строк и чисел. Сложение чисел – известная операция. Программисты привыкли и к сложению строк. Сложение строк имеет совсем другой смысл, нежели сложение чисел. Нельзя, например, ввести операцию вычитания, так что бы она была применима к любой паре строк.
Не смотря на такие различия, в программе сложение строк и чисел обозначается совершенно одинаково – значком ‘+’, что очень удобно при чтении и написании текста программы.
Такой вид полиморфизма называется полиморфизмом перегрузки (одно и то же имя ‘+’ используется с разными типами данных).
Шаблонный полиморфизм встречался в наших примерах, когда мы использовали обобщенные контейнеры List. Там в угловых скобках ‘<>’, указывается тип-параметр. Для списка (list) он определяет тип элементов, которые будут содержаться в списке.
Последний вид полиморфизма, который мы рассмотрим – полиморфизм виртуальных методов или ‘чистый’ полиморфизм.
Для его демонстрации рассмотрим приложение, которой позволяет создавать графические рисунки, помещая на холст различные графические объекты (треугольники, овалы, прямоугольники и пр.) и изменяя их свойства.
Тогда сама картина представляет собой список фигур, которые нужно нарисовать (будем использовать русские слова, для большей ясности примеров).
List<Фигура> Рисунок;
Основной цикл отрисовки картины в таком случае будет иметь вид
foreach(Фигура ф in Рисунок)
{
ф.Нарисуйся();
}
Фрагмент 7. Цикл рисования картины. В учебных целях названия переменных, методов и классов русские.
Получается, что каждая фигура должна уметь нарисовать себя и иметь метод Нарисуйся. Создадим соответствующую иерархию классов.
public class Фигура
{
public void Нарисуйся(){…};
}
public class Треугольник: Фигура
{
public void Нарисуйся(){…};
}
public class Эллипс: Фигура
{
public void Нарисуйся(){…};
}
public class Прямоугольник: Фигура
{
public void Нарисуйся(){…};
}
Фрагмент 8. Иерархия классов фигур.
Теперь, заполнив список фигур конкретными фигурами (см. фрагмент 9), мы ожидаем, что код фрагмента 7 нарисует нам целую картину.
Рисунок.Add (new Треугольник());
Рисунок.Add (new Эллипс ());
Рисунок.Add (new Эллипс());
Рисунок.Add (new Прямоугольник());
Рисунок.Add (new Треугольник());
Фрагмент 9. Заполнение картины конкретными фигурами. В список фигур можно помещать объекты указанных типов, т.к. они являются производными от Фигура.
Однако, к нашему разочарованию код фрагмента 7 не сработает, т.к. компилятор будет знать, что объекты которые должны нарисоваться имеют тип Фигура и вызовет метод рисования фигуры. А поскольку абстрактная фигура, скорее всего, ничего не рисует, то картина окажется пустой!
Что бы исправить ситуацию нужно метод Нарисуйся в базовом классе объявить как виртуальный (virtual), а в остальных классах отметить его как override (перекрыть)
public class Фигура
{
public virtual void Нарисуйся(){…};
}
public class Треугольник: Фигура
{
public override void Нарисуйся(){…};
}
public class Эллипс: Фигура
{
public override void Нарисуйся(){…};
}
public class Прямоугольник: Фигура
{
public override void Нарисуйся(){…};
}
Фрагмент 10. Иерархия классов фигур. Метод ‘Нарисуйся’ в базовом классе объявлен как виртуальный, а в дочерних классах перекрыт.
Работает этот механизм так. В каждом классе имеется таблица виртуальных методов. Она содержит адреса методов, которые объявлены как виртуальные. Производные классы копируют таблице виртуальных методов и модифицируют ее добавляя новые виртуальные метода (объявленные как virtual) и заменяя адреса методов, указанных для перекрытия (override).
Каждый объект имеет ссылку на таблицу виртуальных методов своего класса.
Компилятор ‘знает’, что метод ‘Нарисуйся’ является виртуальным, поэтому для его вызова он не может использовать адрес ‘Нарисуйся’ класса ‘Фигура’. Вместо этого, вставляется код поиска адреса метода для фактического объекта находящегося в списке фигур. Поиск заключается в чтении таблицы виртуальных методов, на которую ссылается объект ‘ф’ выбор из нее адреса метода ‘Нарисуйся’.
Таким образом, для каждой фигуры будет вызван метод соответствующего класса и мы увидим картину. Этот механизм называется ‘поздним связыванием’, что означает определение адреса метода не во время компиляции, а во время исполнения программы.
Отметим, что применение чистого полиморфизма основано на виртуальных методах и иерархии (т.е. наследовании) классов. Это вторая важна роль наследования.
Чистый полиморфизм позволил нам написать ‘обобщенный код’ (фрагмент 7), который будет успешно работать не зависимо от того, сколько других потомков класса ‘Фигура’ мы создадим. Использование обобщенных программ (основанных на обобщенном коде) может расширяться уже после того, как программа была разработана и поступила в эксплуатацию.
Примеров таких программ множество. Например, каждый Web browser может расширяться plugin’ами. Естественно, разработчики браузера не собирали информацию о том, кто хочет создать расширения для него. Они просто определили интерфейс плагина, и компании, которые хотя расширять браузер создают классы потомки от этого интерфейса и внедряют их в браузер.
Упражнения для подготовки к экзамену
Объясните, что дает каждый из принципов ООП для разработки программ?
Подготовьте собственный пример программ, которые могут расширяться после того, как они разработаны и поступили в эксплуатацию.