Опубликован: 19.08.2004 | Уровень: для всех | Доступ: платный | ВУЗ: Национальный исследовательский ядерный университет «МИФИ»

Лекция 10: Концепция полиморфизма и ее реализация в языке C#

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

Формализуем понятие полиморфизма применительно к объектно-ориентированному подходу. Напомним, что история развития теоретических основ полиморфизма изложена во вступительной лекции.

Под полиморфизмом будем иметь в виду возможность оперирования объектами без однозначной идентификации их типов.

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

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

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

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

С точки зрения практической реализации концепции полиморфизма в языке программирования C# в форме полиморфных функций особое значение для исследования имеет механизм интерфейсов.

Напомним, что реализация полиморфизма при функциональном подходе к программированию основана на оперировании функциями переменного типа.

Для иллюстрации исследуем поведение встроенной SML-функции hd (от слова "head" - голова), которая выделяет "голову" (первый элемент) списка, вне зависимости от типа его элементов. Применим функцию к списку из целочисленных элементов:

hd [1, 2, 3]; 
val it = 1: int

Получим, что функция имеет тип функции из списка целочисленных величин в целое число:

int list -> int

В случае списка из значений истинности та же самая функция

hd [true, false, true, false];
val it = true: bool

возвращает значение истинности, т.е. имеет следующий тип:

bool list -> bool

Наконец, для случая списка кортежей из пар целых чисел

hd [(1,2)(3,4),(5,6)];
val it = (1,2) : int*int

получим тип

((int*int)list -> (int*int))

В итоге можно сделать вывод, что функция hd имеет тип

(type list) -> type

где type - произвольный тип, т.е. функция hd полиморфна.

Проиллюстрируем сходные черты и особенности реализации концепции полиморфизма при функциональном и объектно-ориентированном подходе к программированию следующим фрагментом программы на языке C#:

void Poly(object o) {
    Console.WriteLine(o.ToString());
}

Как видно, приведенный пример представляет собой описание полиморфной функции Poly, которая выводит на устройство вывода (например, на экран) произвольный объект o, преобразованный к строковому формату ( o.ToString() ).

Рассмотрим ряд примеров применения функции Poly:

Poly(25);
Poly("John Smith");
Poly(3.141592536m);
Poly(new Point(12,45));

Заметим, что независимо от типа аргумента (в первом случае это целое число 25, во втором - символьная строка "John Smith", в третьем - вещественное число π=3.141592536, в четвертом - объект типа Point, т.е. точка на плоскости с координатами (12,45) ) обработка происходит единообразно и, как и в случае с языком функционального программирования SML, функция генерирует корректный результат.

Как видно из приведенных выше примеров, концепция полиморфизма одинаково применима как к функциональному, так и к объектно-ориентированному подходу к программированию. При этом целью полиморфизма является унификация обработки разнородных языковых объектов, которые в случае функционального подхода являются функциями, а в случае объектно-ориентированного - объектами переменного типа. Отметим, что для реализации полиморфизма в языке объектно-ориентированного программирования C# требуется четкое представление о ряде понятий и механизмов.

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

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

Еще одним значимым механизмом, сопряженным с полиморфизмом, является так называемое отложенное связывание (или, иначе, "ленивые" вычисления ), в ходе которых значения присваиваются объектам (т.е. связываются с ними) по мере того как эти значения требуются во время выполнения программы.

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

При вычислении с вызовом по значению (call-by-value) все выражения должны быть означены до вычисления операции аппликации. Заметим, что формализация стратегии вычислений с вызовом по значению возникла в числе первых моделей computer science в виде абстрактной SECD-машины П.Лендина.

При вычислении с вызовом по имени (call-by-name) до вычисления операции аппликации необходима подстановка термов вместо всех вхождений формальных параметров до означивания . Стратегию вычислений с вызовом по имени иначе принято называть вызовом по ссылке (call-by-reference).

Наконец, при вычислении с вызовом по необходимости (call-by-need) ранее вычисленные значения аргументов сохраняются в памяти компьютера только в том случае, если необходимо их повторное использование. Именно эта стратегия лежит в основе "ленивых" (lazy), "отложенных" (delayed) или "замороженных" (frozen) вычислений, которые принципиально необходимы для обработки потенциально бесконечных структур данных.