Основы программирования — второй семестр 08-09; Михалкович С.С.; VIII часть
Содержание
Полиморфизм и виртуальные методы
Вводные понятия
Для объектно-ориентированного программирования часто приводят следующую формулу:
ООП = инкапсуляция + наследование + полиморфизм
Рассмотрим последнюю часть этой формулы - полиморфизм.
- Полиморфизм дословно означает многообразие форм.
Более точно, полиморфизм — это способность родственных классов выполнять действия с одинаковыми именами сходным образом.
Пример.
Есть класс
Студент Готовиться_к_экзамену()
И два его наследника:
Хороший_студент Готовиться_к_экзамену()
и
Плохой студент Готовиться_к_экзамену()
Действие Готовиться_к_экзамену() они выполняют по-разному.
Рассмотрим следующий код:
var p: Person;
p := new Student('Иванов', 17, 1, 11);
p.Print();
Вопрос: какой метод Print вызовется?
Ответ: В разных языках программирования вызовутся Print разных классов:
- в таких языках, как Java, Eiffel — Student.Print
- а в C++, C#, PasacalABC.NET — Person.Print
Т.о. в PascalABC.NET вызовется метод Person.Print, но, хотелось бы, чтобы вызывался метод Student.Print.
- Если решение о том, какой метод вызывать, принимается на этапе компиляции (рано), то связывание имени метода с конкретным кодом называется ранним связыванием.
- Если же решение о том, какой метод вызывать, принимается на этапе выполнения программы (поздно), то связывание имени метода с конкретным кодом называется поздним связыванием.
Позднее связывание осущесвляется с методом того класса, на который ссылается переменная в процессе выполнения программы.
Итак, в PascalABC.NET по умолчанию реализовано раннее связывание.
Позднее связывание и виртуальные методы
Чтобы обеспечить позднее связывание в языке Pascal, соответствующие методы надо сделать виртуальными в базовом классе.
type
Person = class
...
public
...
procedure Print; virtual;
begin
WriteFormat('Имя: {0} Возраст: {1} ',
fName, fAge);
end;
end;
Student = class(Person)
...
public
procedure Print; override;
begin
inherited Print;
WriteFormat('Курс: {0} Группа: {1} ',
fCourse, fGroup);
end;
end;
При переопределении виртуального метода в классе-потомке используется ключевое слово override.
Примечание. Переопределяющий виртуальный метод должен иметь те же параметры и тот же тип возвращаемого значения.
Вернемся к рассмотренному ранее коду:
var p: Person;
p := new Student('Иванов', 17, 1, 11);
p.Print();
Теперь решение о том, какой метод вызывать, будет отложено до этапа выполнения, и будет вызвано Print того класса, на объект которого ссылается переменная в текущий момент (в данном случае — для Student).
Вызов виртуального метода осуществляется немного медленнее обычного метода.
Полиморфизм в объектно-ориентированных программирования реализуется через механизм виртуальных методов.
- Переменная базового класса, содержащая виртуальные методы, называется полиморфной переменной.
Она имеет статический тип (заявленный при объявлении) и динамический (тип объекта, на который она ссылается в данный момент выполнения программы).
Замечание. Обычная подпрограмма также может быть полиморфной.
Пример.
procedure Print(p: Person);
begin
p.Print(); // обращение полиморфизма
end;
...
Print(new Student(...));
- Обычная подпрограмма называется полиморфной, если она содержит хотя бы один полиморфный параметр.
Виртуальные методы как блоки замены кода
Рассмотрим следующий полиморфный метод:
procedure polymorph(p: Base);
begin
p.Method1;
p.Method2;
p.Method3;
end;
Напишем класс StudentInSession, унаследовав его от Base:
procedure polymorph(p: Base);
type
Base = class
public
procedure Method1; virtual;
procedure Method2; virtual;
procedure Method3; virtual;
end;
StudentInSession = class(Base)
public
procedure Method1; override;
begin
writeln('Sleep');
end;
procedure Method2; override;
begin
writeln('Eat');
end;
procedure Method3; override;
begin
writeln('Think');
end;
end;
...
polymorph(new StudentInSession);
Места вызовов виртуальных методов в некотором коде могут быть заменены все одновременно с использованием следующего приема:
- Определим от базового класса, объект которого вызывает эти методы, потомка, в котором переопределим данные виртуальные методы.
- При конструировании вместо объекта базового класса создадим объект потомка и присвоим его указанной полиморфной переменной. При этом все вызовы виртуальных методов будут вызывать код для потомка.
Замечание. Код, содержащий вызовы виртуальных методов, всегда проектируется с учетом будущих изменений. При создании, в будущем, нового потомка этот код может вести себя совершенно по-другому, выполняя лишь основную роль. Поэтому мы всегда, когда пишем код с вызовами виртуальных методов, думаем о будущем.
Класс Base создавался только для того, чтобы его виртуальные методы были переопределены в потомке.
- Виртуальные методы, предназначенные для переопределения в потомке и ничего не выполняющие, называются абстрактными, а класс, их содержащий — абстрактным классом.
Объекты абстрактных классов создавать нельзя.
В соответствии с этим, сделаем наш базовый класс Base абстрактным:
type Base = class
public
procedure Method1; virtual; abstract;
procedure Method2; virtual; abstract;
procedure Method3; virtual; abstract;
end;
Класс Object — неявный предок всех классов .NET
Все классы в PascalABC.NET, если не указан другой предок, наследуются от класса Object.
Т.е. и integer, и string — классы.
<xh4> Интерфейс класса Object </xh4>
Object = class
function Equals(o: Object): boolean; virtual;
function ToString: string; virtual;
function GetType: System.Type;
Примечание. Как нам известно, использовать ключевые слова в качестве идентификаторов запрещено. Однако, можно использовать перед ними символ «&». Тогда можно, например, описать переменную &type.
Метод Equals
- Сравнивает текущий объект с объектом o и, если они равны, возвращает true, иначе — false.
- По умолчанию, все встроенные (а именно — размерные) типы и тип string сравниваются по значению, а все классы — по ссылке (две переменные считаются равными, если ссылаются на один объект).
Это можно изменить, переопределив метод Equals в потомке.
Метод ToString
- Возвращает строковое представление объекта.
Если не переопределен, то возвращает имя типа.
Метод GetType
- Возвращает объект типа System.Type, который характеризует тип данного объекта.
Пример.
var i: integer := 5;
begin
var s: string := i.ToString;
end.
<xh4> Переопределение методов Equals и ToString в классах Person и Student </xh4>
type Person = class
...
public
...
function Equals(o: object): boolean;
begin
if o = nil then
Result := false
else if GetType <> o.GetType then
Result := false
else
begin
var p := Person(o);
Result := (Name = p.Name) and (Age = p.Age);
end;
end;
function ToString: string;
begin
Result := Format(
'Имя: {0} Возраст: {1} ',
fName, fAge);
end;
type Student = class(Person)
...
public
...
function Equals(o: object): boolean;
begin
Result := inherited Equals(o);
if Result then
begin
var s := Student(o);
Result := (Course = s.Course) and (Group = s.Group);
end;
end;
function ToString: string;
begin
Result := inherited ToString + Format(
'Курс: {0} Группа: {1} ',
fCourse, fGroup);
end;
Полный текст новой версии классов Person и Student