#31 Интерфейсы в Паскале, реализация их классами в ООП

Интерфейс -- структура для описания типа данных, описывает сигнатуры всех методов, которыми должен обладать класс, чтобы относится к данному типу.

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

Конкретная релализация
методов интерфейса оставляется классам, которые будут, как говорят, реализовывать данный интерфейс.

Перейдем к рассмотрению примеров.

Пример №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.

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

  • не нужно (по факту и нельзя) реализовывать ничего в обобщенном типе (если нужна конкретная реализация - то как раз надо использовать класс) - это хорошо если вам и не нужна никак "реализация" по умолчанию (типа возвращения нулевого веса как в примере с наследованием выше)
  • Не нужны специальные ключевые слова для переопределения реализаций (опять, же см. пример с наследованием выше)
  • Общие методы не нужно добавлять в родительские классы (а если такие классы есть - то это может быть проблемой, напр. потому что родительских классов станет слишком много или они станут очень большими и трудночитаемыми)
  • В отличие от только одного возможного родителя, у класса может быть много реализуемых интерфейсов. Фактически это и позволяет "обобщать" тип "помимо" наследования от общего предка.

Подробности:

Key Words for FKN + antitotal forum (CS VSU):