Библиотека программиста

«Ни один ремесленник, который стремится к вершинам своей профессии, не примет негодных инструментов; и ни один производитель, который ценит качество работы, не будет упрашивать ремесленника принять их»

Главная страница > Технология COM > 43. Тип interface в Object Pascal

43. Тип interface в Object Pascal

В языке Object Pascal начиная с версии Delphi 3 за ключевым словом interface, которое прежде использовалось только для объявления начала интерфейсной секции модуля unit, было закреплено еще одно значение – имя типа, больше всего подобное классу class. В этом значении термин interface является краеугольным камнем тенологии COM (Component Object Model – модель объектных компонент). В этом разделе мы еще не будем касаться собственно технологии COM, а разберемся с тем, как можно использовать этот тип в программе и какую пользу из этого можно извлечь.

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

type

имя_типа = interface (интерфейс-предок)

['{GUID}']

описание компонент

end;

Как видно из приведенного формата, описание типа interface очень похоже на описание типа class. Пропустим пока обсуждение фрагмента описания ['{GUID}'] и оговорим правила описания интерфейса:

1. По негласному соглашению имя типа interface принято начинать с заглавной буквы I, подобно тому, как имя типа класс принято начинать с заглавной буквы T.

2. Любой интерфейс имеет предком базовый интерфейс IUnknown, как и любой класс – базовый класс TObject.

3. В отличие от класса экземпляр интерфейса создать невозможно.

4. Компонентами интерфейса могут быть только методы – процедуры или функции, а также свойства property. Никакие другие компоненты не могут быть объявлены в интерфейсе.

5. Методы интерфейса по умолчанию предполагаются виртуальными и абстрактными, но директива virtual, как и директивы abstract, dynamic, override или reintroduce не должны и не могут быть указаны. Вместе с тем допускается указание директив, регламентирующих соглашение по вызовам (обычно это stdcall или safecall).

6. Все методы интерфейса предполагаются общедоступными (public), но никакие директивы разграничения доступа указывать не допускается, даже public.

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

Смысл и назначение интерфейса в Object Pascal наиболее близок к понятию абстрактного класса. Вот абстрактный пример абстрактного класса:

type

TAbstractClass = class

Procedure MyProc(Src : string); virtual; abstract;

end;

Такое описание компилируется без ошибок, причем реализация метода TAbstractClass.MyProc не требуется и она не допускается. Можно даже описать экземпляр класса var AbstractClass : TAbstractClass и даже создать экземпляр этого класса AbstractClass:=TAbstractClass.Create, получив при этом предупреждение компилятора (Constructing instance of 'TAbstractClass' containing abstract method 'TAbstractClass.MyProc' – создание экземпляра класса, содержащего абстрактный метод). Вместе с тем попытка вызова абстрактного метода AbstractClass.SomeProc(‘’) вызовет ошибку времени выполнения.

Какова цель описания абстрактного класса? Такой класс является базой для создания классов наследников и декларирует их свойства и поведение, но не содержит конкретную реализацию этого поведения. Другими словами, абстрактный класс служит своего рода декларацией о намерениях, а его наследники должны эти намерения воплотить в жизнь путем реализации заявленных абстрактных методов.

Простейший пример использования типа Interface в программе.

Создадим типичное приложение и добавим в него описание таких классов и их реализацию:

type

TForm1 = class(TForm)

Button1: TButton;

Procedure Button1Click(Sender: TObject);

end;

ITest = interface

['{226FEB40-E9ED-11D7-846C-C1A00994643B}'] {не обязательно}

Procedure Beep;

end;

TTest = class (TInterfacedObject,ITest)

Procedure Beep;

Destructor Destroy; override;

end;

var

Form1: TForm1;

implementation

{$R *.dfm}

Procedure TTest.Beep;

Begin

Windows.Beep(2000,500);

End;

Procedure TForm1.Button1Click(Sender: TObject);

var Test : ITest;

Begin

Test:=TTest.Create;

Test.Beep;

End;

Destructor TTest.Destroy;

Begin

ShowMessage('Destroy was called');

inherited;

End;

END.

Прежде всего отметим, что описание класса ITest несколько отличается от описания «обычного» класса тем, что в нем присутствует GUID (его значение в Delphi генерируется с помощью клавишной команды Ctrl+Shift+G), хотя в данном примере его можно было бы и опустить. Вторая особенность класса ITest заключается в том, что метод Beep в нем просто декларируется, а реализация этого метода должна быть выполнена в другом классе.

У описания класса TTest также есть особенность: два «родителя». Такое в Object Pascal возможно только в том случае, когда дополнительный порождающий класс имеет тип интерфейс и базовым классом является также класс TInterfacedObject. Класс TInterfacedObject служит простым и удобным базовым классом, так как он содержит реализацию трех базовых методов интерфейса IInterface: QueryInterface, AddRef и Release. Вот описание этого класса:

TInterfacedObject = class(TObject, IInterface)

protected

FRefCount: Integer;

function QueryInterface(const IID: TGUID; out Obj): HResult; stdcall;

function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

public

procedure AfterConstruction; override;

procedure BeforeDestruction; override;

class function NewInstance: TObject; override;

property RefCount: Integer read FRefCount;

end;

При запуске приложения на выполнение можно заметить еще одну особенность – автоматический вызов деструктора класса TTest в тот момент, когда локальная переменная Test перестает существовать при завершении процедуры Button1Click. В режиме пошагового выполнения программы (при включенном режиме Project Options►Compiler►Use debug DCUs) можно заметить, что вызов деструктора выполняет метод IInterface._Release функции System._IntfClear. Приведенный пример приложения показывает, между прочим, как можно в Object Pascal добиться автоматического вызова деструктора (как в С++).

Отметим также, что класс, производный от TInterfacedObject, может включать произвольное число интерфейсов, а не только один, как в приведенном примере.

Процедура Button1Click может быть реализована и таким образом:

Procedure TForm1.Button1Click(Sender: TObject);

var

Test : TTest;

IObj : ITest;

Begin

Test:=TTest.Create;

if Test.GetInterface(ITest,IObj) then IObj.Beep

else ShowMessage('Интерфейс ITest не поддерживается');

End;

В этом варианте показано, как можно проверить наличие интерфейса ITest с помощью метода TObject.GetInterface, который возвращает значение true в случае наличия запрашиваемого интерфейса и false – в противном. Несмотря на то, что первый параметр метода GetInterface объявлен как имеющий тип TGuid, вместо него можно передавать имя класса, содержащего описание запрашиваемого интерфейса.

Возникает естественный вопрос: какова практическая польза от класса TInterfacedObject, если не считать предоставляемой им возможности автоматического вызова деструктора? Все зависит от фантазии программиста. Например, с помощью этого средства можно обеспечить единообразное взаимодействие нескольких классов приложения, например, форм, если каждый из этих классов будет включать один и тот же общий интерфейс и собственную реализацию всех или некоторых методов этого интерфейса (см. раздел «Использование интерфейсов внутри программы» в работе «Delphi6 и технология COM»). Другой пример – реализация модулей расширения программы (plug-ins) из той же работы.





<< Предыдущая статья
«42. Межпроцессное взаимодействие»
Следующая статья >>
44. Введение в COM технологии