
- •Лекции по курсу "Технология программирования" (1-й семестр) Оглавление
- •Технология .Net Предыдущее состояние дел.
- •Главные компоненты платформы .Net (clr, cts и cls)
- •Общеязыковая среда выполнения (clr)
- •О бщая система типов и общеязыковые спецификации (cts и cls)
- •Библиотека базовых классов
- •Роль языка с#
- •Компоновочные блоки
- •Роль метаданных типов .Net
- •Роль манифеста компоновочного блока
- •Общая система типов.
- •Объектно-ориентированное программирование
- •Главные элементы объектно-ориентированного подхода
- •Дополнительные элементы ооп
- •Принципы объектно-ориентированного программирования.
- •Классы Инкапсуляция
- •Объект (экземпляр класса).
- •Ключевое слово this
- •Отношения между объектами.
- •Основные отличительные особенности класса
- •Спецификаторы доступа
- •Состав класса
- •Поля класса
- •Доступ к полям
- •Статические и экземплярные переменные
- •Методы (функции-члены класса)
- •Переменное число параметров метода
- •Статические методы
- •Конструкторы
- •Закрытые конструкторы или классы без экземпляров
- •Статические конструкторы.
- •Деструкторы
- •Абстрактные методы и классы.
- •Свойства
- •Индексаторы
- •Статические классы
- •Частичные классы
- •Рекомендации по программированию
- •Наследование Понятие наследования в программировании
- •Типы наследования
- •Наследование реализации
- •Определение наследующих классов
- •Уровень доступа protected и internal
- •Ссылка на объект базового класса
- •Протоклассы
- •Предотвращение наследования с помощью ключевого слова sealed.
- •Отношения между классами
- •Абстрактные классы.
- •Класс object
- •Функциональные замыкания
- •Разработка функциональных замыканий с помощью наследования
- •Разработка функциональных замыканий с помощью экземпляров класса
- •Заключение.
- •Полиморфизм
- •Полиморфизм наследующих классов.
- •Переопределение методов родительского класса. Раннее связывание.
- •Виртуальные методы и их переопределение.
- •Как вызывают виртуальные методы
- •Виртуальные функции и принцип полиморфизма
- •Перегрузка.
- •Перегруженные конструкторы
- •Рекомендации программисту.
Полиморфизм
Под полиморфизмом в объектно-ориентированном программировании понимают способность одного и того же программного текста 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В(); }// вызов функции класса В