Файл: Учебное пособие для студентов Авторы А. Н. Вальвачев, К. А. Сурков, Д. А. Сурков, Ю. М. Четырько Содержание Содержание 1.doc

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 04.05.2024

Просмотров: 175

Скачиваний: 4

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.
разрешено инициализировать строковым представлением глобально-уникального идентификатора. Компилятор сам преобразует строку в запись с типом TGUID. Пример:

const

InterfaceID: TGUID = '{DC601962-28E5-4BF7-9583-0CE22B605045}';

Если глобально-уникальный идентификатор назначается интерфейсу, то он записывается после ключевого слова interface и заключается в квадратные скобки, например:

type

IInterface = interface

['{00000000-0000-0000-C000-000000000046}']

...

end;

В будущем нашему интерфейсу ITextReader понадобится глобально-уникальный идентификатор. Но как его выбрать так, чтобы он оказался уникальным? Очень просто — нажмите в редакторе кода комбинацию клавиш Ctrl+Shift+G.

type

ITextReader = interface

['{DC601962-28E5-4BF7-9583-0CE22B605045}'] // Результат нажатия Ctrl+Shift+G

...

end;

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

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

Если у интерфейса есть глобально-уникальный идентификатор, то программный идентификатор интерфейса можно использовать там, где ожидается тип данных TGUID, например:

const

IID_ITextReader: TGUID = '{DC601962-28E5-4BF7-9583-0CE22B605045}';
function TestInterface(const IID: TGUID): Boolean;
begin

...

TestInterface(ITextReader);

// эквивалентно

TestInterface(IID_ITextReader);

...

end;

6.5. Реализация интерфейса

Интерфейс бесполезен до тех пор, пока он не реализован. Реализацией интерфейса занимается класс. Если класс реализует интерфейс, то интерфейс может использоваться для доступа к объектам этого класса. При объявлении класса имя реализуемого интерфейса записывается через запятую после имени базового класса:

type

TTextReader = class(TObject, ITextReader)

...

end;

Такая запись означает, что класс TTextReader унаследован от класса TObject и реализует интерфейс ITextReader (см. рисунок 6.1).



Рисунок 6.1. Класс TTextReader унаследован от класса TObject и реализует интерфейс ITextReader. Сплошными линиями отмечено наследование классов, а пунктирной линией — реализация интерфейса классом.


Класс, реализующий интерфейс, должен содержать код для всех методов интерфейса. Класс TTextReader в модуле ReadersUnit (см. главу 3) вроде бы содержит код для всех методов интерфейса ITextReader, и все, что нужно сделать, — это добавить имя интерфейса в заголовок класса. Сделайте это в модуле ReadersUnit:

unit ReadersUnit;
interface
type

ITextReader = interface

...

end;
TTextReader = class(TObject, ITextReader)

...

end;

Если класс содержит только часть методов интерфейса, то недостающие методы придется добавить. Так в интерфейсе ITextReader описан метод GetActive, а в классе TTextReader такого метода нет. Добавьте метод GetActive в класс TTextReader:

type

TTextReader = class(TObject, ITextReader)

...

function GetActive: Boolean;

...

end;
function TTextReader.GetActive: Boolean;

begin

Result := FActive;

end;

Но это еще не все. Мы совсем забыли о методах QueryInterface, _AddRef и _Release, которые тоже должны быть реализованы. К счастью, вам нет необходимости ломать голову над реализацией этих методов, поскольку разработчики системы Delphi уже позаботились об этом. Стандартная реализация методов интерфейса IInterface находится в классе TInterfacedObject. Мы его рассмотрим ниже, а сейчас просто унаследуем класс TTextReader от класса TInterfacedObject — и он получит готовую реализацию методов QueryInterface, _AddRef и _Release.

type

TTextReader = class(TInterfacedObject, ITextReader)

...

end;

Теперь реализация интерфейса ITextReader полностью завершена и можно переходить к использованию объектов класса TTextReader через этот интерфейс.

6.6. Использование интерфейса

Для доступа к объекту через интерфейс нужна интерфейсная переменная:

var

Intf: ITextReader;

Интерфейсная переменная занимает в оперативной памяти четыре байта, хранит ссылку на интерфейс объекта и автоматически инициализируется значением nil.

Перед использованием интерфейсную переменную инициализируют значением объектной переменной:

var

Obj: TTextReader; // объектная переменная

Intf: ITextReader; // интерфейсная переменная

begin

...

Intf := Obj;

...

end;

После инициализации интерфейсную переменную Intf можно использовать для вызова методов объекта Obj:

Intf.Active := True; // -> Obj.SetActive(True);

Intf.NextLine; // -> Obj.NextLine;

Через интерфейсную переменную доступны только те методы и свойства объекта, которые есть в интерфейсе:

Intf.Free; // Ошибка! У интерфейса ITextReadaer нет метода Free.

Obj.Free; // Метод Free можно вызвать только так.

6.7. Реализация нескольких интерфейсов

Один класс может содержать реализацию нескольких интерфейсов. Такая возможность позволяет воплотить в классе несколько понятий. Например, класс
TTextReader — "считыватель табличных данных" — может выступить еще в одной роли — "считыватель строк". Для этого он должен реализовать интерфейс IStringIterator:

type

IStringIterator = interface

function Next: string;

function Finished: Boolean;

end;

Интерфейс IStringIterator предназначен для последовательного доступа к списку строк. Метод Next возвращает очередную строку из списка, метод Finished проверяет, достигнут ли конец списка.

Реализуем интерфейс IStringIterator в классе TTextReader таким образом, чтобы последовательно считывались значения из ячеек таблицы. Например, представьте, что в некотором файле дана таблица:

Aaa Bbb Ccc

Ddd Eee Fff

Ggg Hhh Iii

Чтение этой таблицы через интерфейс IStringIterator вернет следующую последовательность строк:

Aaa

Bbb

Ccc

Ddd

Eee

Fff

Ggg

Hhh

Iii

Ниже приведен программный код, обеспечивающий поддержку интерфейса IStringIterator в классе TTextReader:

type

TTextReader = class(TInterfacedObject, ITextReader, IStringIterator)

FColumnIndex: Integer;

function Next: string;

function Finished: Boolean;

...

end;

...

function TTextReader.Next: string;

begin

if FColumnIndex = ItemCount then // Если пройден последний элемент текущей строки,

begin // то переходим к следующей строке таблицы

NextLine;

FColumnIndex := 0;

end;

Result := Items[FColumnIndex];

FColumnIndex := FColumnIndex + 1;

end;
function TTextReader.Finished: string;

begin

Result := EndOfFile and (FColumnIndex = ItemCount);

end;

Теперь объекты класса TTextReader совместимы сразу с тремя типами данных: TInterfacedObject, ITextReader, IStringIterator.

var

Obj: TTextReader;

Reader: ITextReader;

Iterator: IStringIterator;

begin

...

Reader := Obj; // Правильно

Iterator := Obj; // Правильно

...

end;

В одном случае объект класса TTextReader рассматривается как считыватель табличных данных, а в другом случае — как обычный список строк с последовательным доступом. Например, если есть две процедуры:

procedure LoadTable(Reader: ITextReader);

procedure LoadStrings(Iterator: IStringIterator);

то объект класса TTextReader можно передать в обе процедуры:

LoadTable(Obj); // Obj воспринимается как ITextReader

LoadStrings(Obj); // Obj воспринимается как IStringIterator

6.8. Реализация интерфейса несколькими классами

Несколько совершенно разных классов могут содержать реализацию одного и того же интерфейса. С объектами таких классов можно работать так, будто у них есть общий базовый класс. Интерфейс выступает аналогом общего базового класса.

Рассмотрим пример. Представьте, что есть два класса: TTextReader и TIteratableStringList:


type

TTextReader = class(TInterfacedObject, ITextReader, IStringIterator)

...

end;
TIteratableStringList = class(TStringList, IStringIterator)

...

end;

Схематично полученную иерархию классов можно представить так (рисунок 6.2):



Рисунок 6.2. Иерархия классов, реализующих интерфейсы. Сплошными линиями отмечено наследование классов, а пунктирными линиями — реализация интерфейсов классами.

Объекты классов TTextReader и TIteratableStringList несовместимы между собой. Тем не менее, они совместимы с переменными типа IStringIterator. Это значит, что если есть процедура:

procedure LoadStrings(Iterator: IStringIterator);

то вы можете передавать ей объекты обоих упомянутых классов в качестве аргумента:

var

ReaderObj: TTextReader;

StringsObj: TIteratableStringList;

begin

...

LoadStrings(ReaderObj); // Все правильно

LoadStrings(StringsObj); // Все правильно

...

end;

6.9. Связывание методов интерфейса с методами класса

Метод интерфейса связывается с методом класса по имени. Если имена по каким-то причинам не совпадают, то можно связать методы явно с помощью специальной конструкции языка Delphi.

Например, в классе TTextReader добавлены методы Next и Finished для поддержки интерфейса IStringIterator. Согласитесь, что существование в одном классе методов Next и NextLine вносит путаницу. По названию метода Next не понятно, что для этого метода является следующим элементом. Поэтому уточним название метода в классе TTextReader и воспользуемся явным связыванием методов, чтобы сохранить имя Next в интерфейсе IStringIterator:

type

TTextReader = class(TInterfacedObject, ITextReader, IStringIterator)

...

function NextItem: string;

function IStringIterator.Next := NextItem; // Явное связывание

end;

При работе с объектами класса TTextReader через интерфейс IStringIterator вызов метода Next приводит к вызову метода NextItem:

var

Obj: TTextReader;

Intf: IStringIterator;

begin

...

Intf := Obj;

Intf.Next; // -> Obj.NextItem;

...

end;

Очевидно, что связываемые методы должны совпадать по сигнатуре (списку параметров и типу возвращаемого значения).

6.10. Реализация интерфейса вложенным объектом

Случается, что реализация интерфейса содержится во вложенном объекте класса. Тогда не требуется программировать реализацию интерфейса путем замыкания каждого метода интерфеса на соответствующий метод вложенного объекта. Достаточно делегировать реализацию интерфейса вложенному объекту с помощью директивы
implements:

type

TTextParser = class(TInterfacedObject, ITextReader)

...

FTextReader: ITextReader;

property TextReader: ITextReader read FTextReader implements ITextReader;

...

end;

В этом примере интерфейс ITextReader в классе TTextParser реализуется не самим классом, а его внутренней переменной FTextReader.

Очевидно, что внутренний объект должен быть совместим с реализуемым интерфейсом.

6.11. Совместимость интерфейсов

Совместимость интерфейсов подчиняется определенным правилам. Если интерфейс создан расширением уже существующего интерфейса:

type

IExtendedTextReader = interface(ITextReader)

...

end;

то интерфейсной переменной базового типа может быть присвоено значение интерфейсной переменной производного типа:

var

Reader: ITextReader;

ExtReader: IExtendedTextReader;

begin

...

Reader := ExtReader; // Правильно

...

end;

Но не наоборот:

ExtReader := Reader; // Ошибка!

Правило совместимости интерфейсов чаще всего применяется при передаче параметров в процедуры и функции. Например, если процедура работает с переменными типа ITextReader,

procedure LoadFrom(const R: ITextReader);

то ей можно передать переменную типа IExtendedTextReader:

LoadFrom(ExtReader);

Заметим, что любая интерфейсная переменная совместима с типом данных IInterface — прародителем всех интерфейсов.

6.12. Совместимость класса и интерфейса

Интерфейсной переменной можно присвоить значение объектной переменной при условии, что объект (точнее его класс) реализует упомянутый интерфейс:

var

Intf: ITextReader; // интерфейсная переменная

Obj: TTextReader; // объектная переменная

begin

...

Intf := Obj; // В переменную Intf копируется ссылка на объект Obj

...

end;

Такая совместимость сохраняется в производных классах. Если класс реализует некоторый интерфейс, то и все его производные классы совместимы с этим интерфейсом (см. рисунок 6.3):

type

TTextReader = class(TInterfacedObject, ITextReader)

...

end;
TDelimitedReader = class(TTextReader)

...

end;
var

Intf: ITextReader; // интерфейсная переменная

Obj: TDelimitedReader; // объектная переменная

begin

...

Intf := Obj;

...

end;



Рисунок 6.3. Классы TTextReader, TDelimitedReader и TFixedReader совместимы с интерфейсом ITextReader

Однако, если класс реализует производный интерфейс, то это совсем не означает, что он совместим с базовым интерфейсом (см. рисунок 6.4):

type

ITextReader = interface(IInterface)

...

end;
IExtendedTextReader = interface(ITextReader)

...

end;
TExtendedTextReader = class(TInterfacedObject, IExtendedTextReader)