учебники, программирование, основы, введение в,

 

Концепция полиморфизма и ее реализация в языке 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) вычислений, которые принципиально необходимы для обработки потенциально бесконечных структур данных.

Рассмотрим более подробно особенности стратегии вычислений с вызовом по значению (call-by-value, CBV).
Прежде всего, в случае вызова по значению формальный параметр является копией фактического параметра и занимает выделенную область в памяти компьютера.
Кроме того, фактический параметр в случае вызова по значению является выражением.
Проиллюстрируем особенности использования стратегии вызова по значению следующим фрагментом программы на языке C#:
void Inc(int x) {
x = x + 1;
}
void f() {
int val = 3;
Inc(val);
// val == 3
}
Как видно из приведенного примера, фрагмент программы на языке C# реализует ввод значений val посредством функции f. Функция Inc является функцией следования (т.е. прибавления единицы для целых чисел).
Отметим, что, несмотря на применение функции Inc к аргументу val, значение переменной val, как явствует из комментария
// val == 3,
остается неизменным. Это обусловлено тем, что при реализации стратегии вызова по значению формальный параметр является копией фактического.
Рассмотрим более подробно особенности стратегии вычислений с вызовом по имени, или, иначе, по ссылке (call-by-reference, CBR).
Прежде всего, в случае вызова по имени формальный параметр является подстановкой (alias) фактического параметра и не занимает отдельной области в памяти компьютера. При реализации данной стратегии вычислений вызывающей функции передается адрес фактического параметра.
Кроме того, фактический параметр в случае вызова по имени должен быть не выражением, а переменной. При этом формальный параметр является копией фактического.
Проиллюстрируем особенности использования стратегии вызова по имени следующим фрагментом программы на языке C#:
void Inc(ref int x) {
x = x + 1;
}
void f() {
int val = 3;
Inc(ref val);
// val == 4
}
Как видно из приведенного примера, фрагмент программы на языке C# реализует преобразование значений val посредством функции f. Функция Inc является функцией следования.
Отметим, что вследствие применения функции Inc к аргументу val, значение переменной val, как явствует из комментария
// val == 4,
изменяется. Это обусловлено тем обстоятельством, что при реализации стратегии вызова по имени формальный параметр является подстановкой фактического.
Рассмотрим более подробно особенности стратегии вычислений с вызовом по необходимости (call-by-need, CBN).
В целом, данная стратегия вычислений сходна с вызовом по имени (или ссылке, call-by-reference, CBR), однако ее реализация имеет две характерные особенности.
Во-первых, в случае вызова по необходимости значение фактического параметра не передается вызывающей функции, т.е. не происходит связывания переменной со значением.
Во-вторых, данная стратегия вычислений неприменима до того, как означивание может быть произведено, т.е. значение фактического параметра может быть вычислено.
Проиллюстрируем особенности использования стратегии вызова по необходимости следующим фрагментом программы на языке C#:
void Read (out int first, out int next) {
first = Console.Read();
next = Console.Read();
}
void f() {
int first, next;
Read(out first, out next);
}
Как видно из приведенного примера, фрагмент программы на языке C# реализует ввод значений first и next посредством функции Read со стандартного устройства ввода. Функция f может быть означена по необходимости, по мере поступления аргументов.
Исследовав особенности реализации различных стратегий вычислений в языке программирования C#, рассмотрим концепцию полиморфизма в соотнесении с механизмом так называемых абстрактных классов.
Абстрактные классы при объектно-ориентированном подходе (в частности, в языке программирования C#) являются аналогами полиморфных функций в языках функционального программирования (в частности, в языке SML) и используются для реализации концепции полиморфизма. Методы, которые реализуют абстрактные классы, также называются абстрактными и являются полиморфными.
Перечислим основные особенности, которыми обладают абстрактные классы и методы в рамках объектно-ориентированного подхода к программированию.
Прежде всего, отметим то обстоятельство, что абстрактные методы не имеют части реализации (implementation).
Кроме того, абстрактные методы неявно являются виртуальными (т.е. как бы оснащенными описателем virtual).
В том случае, если внутри класса имеются определения абстрактных методов, данный класс необходимо описывать как абстрактный. Ограничений на количество методов внутри абстрактного класса в языке программирования C# не существует.
Наконец, в языке программирования C# запрещено создание объектов абстрактных классов (как конкретизаций или экземпляров).
Проиллюстрируем особенности использования абстрактных классов следующим фрагментом программы на языке C#:
abstract class Stream {
public abstract void
Write(char ch);
public void WriteString(string s)
{
foreach (char ch in s)
Write(s);
}
}

class File : Stream {
public override void Write(char ch)
{
...
write ch to disk
...
}
}
Как видно из приведенного примера, фрагмент программы на языке C# представляет собой описание абстрактных классов Stream и File, реализующих потоковую запись (метод Write) данных в форме символьных строк ch.
Заметим, что описание абстрактного класса Stream реализовано явно посредством зарезервированного слова abstract. Оператор foreach ... in реализует последовательную обработку элементов.
Необходимо обратить внимание на то обстоятельство, что поскольку в производных классах необходимо замещение методов, метод Write класса File оснащен описателем override.
Подводя итоги рассмотрения основных аспектов концепции полиморфизма в объектно-ориентированном подходе к программированию и особенностей реализации этой концепции применительно к языку программирования C#, кратко отметим достоинства полиморфизма.
Прежде всего, к преимуществам концепции полиморфизма следует отнести унификацию обработки объектов различной природы. В самом деле, абстрактные классы и методы позволяют единообразно оперировать гетерогенными данными, причем для адаптации к новым классам и типам данных не требуется реализации дополнительного программного кода.
Кроме того, важным практическим следствием реализации концепции полиморфизма для экономики программирования является снижение стоимости проектирования и реализации программного обеспечения.
Еще одно достоинство полиморфизма – возможность усовершенствования стратегии повторного использования кода. Код с более высоким уровнем абстракции не требует существенной модификации при адаптации к изменившимся условиям задачи или новым типам данных.
Важно также отметить, что идеология полиморфизма основана на строгом математическом фундаменте (в частности, в виде формальной системы ламбда-исчисления), что обеспечивает интуитивную прозрачность исходного текста для математически мыслящего программиста, а также верифицируемость программного кода.
Наконец, концепция полиморфизма является достаточно универсальной и в равной степени применима для различных подходов к программированию, включая функциональный и объектно-ориентированный.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика