#31 Интерфейсы в Паскале, реализация их классами в ООП
Primary tabs
Интерфейс -- структура для описания типа данных, описывает сигнатуры всех методов, которыми должен обладать класс, чтобы относится к данному типу.
Можно сказать, что интерфейс как и класс является помимо прочего является типом данных, но при этом не содержит реализации объявленных методов.
Конкретная релализация методов интерфейса оставляется классам, которые будут, как говорят, реализовывать данный интерфейс.
Перейдем к рассмотрению примеров.
Пример №1 - Общий родитель для общего "интерфейса"
Пусть у нас есть код, в котором фигурируют объекты двух классов особо не связанных в терминах родитель-потомок (и в смысле общего жизненного опыта и в смысле логики наследования):
type
Cat = class // кот
public
procedure saySomething();
end;
Man = class // человек
public
procedure sayHello();
end;
procedure Cat.saySomething();
begin
writeln('Привет, я кот!');
end;
procedure Man.sayHello();
begin
writeln('Привет, я человек!');
end;
var
SomeMan: Man;
CatItem: Cat;
begin
SomeMan := Man.create();
CatItem := Cat.create();
SomeMan.sayHello();
CatItem.saySomething();
end.
-- давайте "придумаем" для этих классов что-то общее, а именно то, что оба объекта обладают массой.
Для примера давайте поставим себе задачу: считать суммарную массу кота и человека, с помощью функции, куда мы будем передавать два объекта (один человек и один кот).
Чтобы посчитать общую массу научим наши объекты сообщать собественную массу - так так это одно и тоже действие "получить вес" - чтобы сигнатура функции завязывалась только на это свойство, вынесем этот метод в общий родительский класс, в итоге получим что-то вроде:
type
WeightObject = class // объект, обладающий массой
public
function getWeight(): integer; virtual; // может быть переопределен
end;
Cat = class(WeightObject) // кот
public
procedure saySomething();
function getWeight(): integer; override;
end;
Man = class(WeightObject) // человек
public
procedure sayHello();
function getWeight(): integer; override;
end;
procedure Cat.saySomething();
begin
writeln('Привет, я кот!');
end;
procedure Man.sayHello();
begin
writeln('Привет, я человек!');
end;
function WeightObject.getWeight(): integer;
begin
result := 0; // нужно переопределить в потомках!
end;
function Man.getWeight(): integer;
begin
result := 90;
end;
function Cat.getWeight(): integer;
begin
result := 4;
end;
{ Функция для вычисление суммарной массы
- указываем тип родительского класса, для аргументов,
т.к. нас интересует единственный метод - получение веса, который
как раз объявлен (еще до любых переопеределений)
в родительском классе WeightObject,
Этот метод - единственное общее чтобы должно у item1 и item2,
всем остальным они могут, если требуется, различаться.
}
function getCommonWeight(item1, item2: WeightObject): integer;
begin
result := item1.getWeight() + item2.getWeight();
end;
var
SomeMan: Man;
CatItem: Cat;
begin
SomeMan := Man.create();
CatItem := Cat.create();
SomeMan.sayHello();
CatItem.saySomething();
writeln(
'Общий вес кота и человека: ',
getCommonWeight(SomeMan, CatItem)
);
end.
- virtual -- ключевое слово, для указание того, что метод может (но не обязан) быть переопределен в потомках, не только для работы с этим потомком непосредственно (там это спец. слово не нужно, все работает автоматически), но и для случая как в функции getCommonWeight() где мы указываем ожидаемый тип переменной как родительский класс WeightObject, а по факту передаем дочерний класс Man
- override -- ключевое слово, которое может быть применено к методу, чтобы он считался переопределяющим (см. описание ситуации пункте для virtual перед данным) для соответствующего virtual-метода из родительского класса
Подробности:
Пример №2 - Интерфейс на замену общему "родителю"
Предыдущий пример можно переписать используя "интерфейсы" в смысле ООП.
Для "обощения сбоку" (а не "сверху" как в наследовании) или "выделения общего в типе для конкретной задачи" (в примере ниже это поиск "суммарной массы"), как раз и можно использовать интерфесы, перепишем последний пример так:
{$interfaces corba}
type
SomeWeight = interface
function getWeight(): integer;
end;
Cat = class(SomeWeight)
public
procedure saySomething();
function getWeight(): integer;
end;
Man = class(SomeWeight)
public
procedure sayHello();
function getWeight(): integer;
end;
procedure Cat.saySomething();
begin
writeln('Привет, я кот!');
end;
procedure Man.sayHello();
begin
writeln('Привет, я человек!');
end;
function Cat.getWeight(): integer;
begin
result := 4;
end;
function Man.getWeight(): integer;
begin
result := 90;
end;
function getCommonWeight(item1, item2: SomeWeight): integer;
begin
result := item1.getWeight() + item2.getWeight();
end;
var
SomeMan: Man;
CatItem: Cat;
begin
SomeMan := Man.create();
CatItem := Cat.create();
SomeMan.sayHello();
CatItem.saySomething();
writeln('Общий вес кота и человека: ', getCommonWeight(SomeMan, CatItem));
end.
Можно добавить еще один объект - коробку, который тоже можно "взвесить":
{$interfaces corba}
type
SomeWeight = interface
function getWeight(): integer;
end;
Cat = class(SomeWeight)
public
procedure saySomething();
function getWeight(): integer;
end;
Man = class(SomeWeight)
public
procedure sayHello();
function getWeight(): integer;
end;
Box = class(SomeWeight)
public
function getWeight(): integer;
end;
procedure Cat.saySomething();
begin
writeln('Привет, я кот!');
end;
procedure Man.sayHello();
begin
writeln('Привет, я человек!');
end;
function Cat.getWeight(): integer;
begin
result := 4;
end;
function Man.getWeight(): integer;
begin
result := 90;
end;
function Box.getWeight(): integer;
begin
result := 20;
end;
function getCommonWeight(item1, item2, item3: SomeWeight): integer;
begin
result := item1.getWeight() + item2.getWeight() + item3.getWeight();
end;
var
SomeMan: Man;
CatItem: Cat;
BoxItem: Box;
begin
SomeMan := Man.create();
CatItem := Cat.create();
BoxItem := Box.create();
SomeMan.sayHello();
CatItem.saySomething();
writeln(
'Общий вес кота и человека и коробки: ',
getCommonWeight(SomeMan, CatItem, BoxItem)
);
end.
-- синтаксически реализация интерфейса похожа на наследования, но есть много плюсы:
- не нужно (по факту и нельзя) реализовывать ничего в обобщенном типе (если нужна конкретная реализация - то как раз надо использовать класс) - это хорошо если вам и не нужна никак "реализация" по умолчанию (типа возвращения нулевого веса как в примере с наследованием выше)
- Не нужны специальные ключевые слова для переопределения реализаций (опять, же см. пример с наследованием выше)
- Общие методы не нужно добавлять в родительские классы (а если такие классы есть - то это может быть проблемой, напр. потому что родительских классов станет слишком много или они станут очень большими и трудночитаемыми)
- В отличие от только одного возможного родителя, у класса может быть много реализуемых интерфейсов. Фактически это и позволяет "обобщать" тип "помимо" наследования от общего предка.
Подробности:
- Log in to post comments
- 1905 reads