#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
- 1129 reads