«Если отладка - процесс удаления ошибок, то программирование должно быть процессом их внесения»
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 технологии