Общие представления об интерфейсах в Object PASCAL

Интерфейс объекта определяют методы и свойства, которые могут быть реализованы классом-наследником этого интерфейса. Они описываются аналогично абстрактным классам, так же, как абстрактные классы, но в отличие от них, не могут иметь экземпляров, не могут иметь реализации никаких своих методов (в Object PASCAL допускается реализация части методов в абстрактные классы). Реализация методов интерфейсов осуществляется в классе, поддерживающем (наследующем) данный интерфейс. Переменная типа интерфейс - это ссылка. Она дает возможность ссылаться на объект, чей класс реализует данный интерфейс. Однако с помощью такой переменной разрешается вызывать только методы, декларированные в данном интерфейсе, а не любые методы данного объекта. Интерфейсы являются альтернативой множественному наследованию, имеют практически все его достоинства и лишены его недостатков. Их использование существенно для написания ПО для распределенных систем на основе COM (the Component Object Model) и CORBA (Common Object Request Broker Architecture). Объекты, поддерживающие интерфейсы, могут взаимодействовать с COM- объектами, написанными на C++ или Java.

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

type

interfaceName =

interface (ancestorInterface)

['{GUID}']

список полей

end;

Отличия от классов:

· Не бывает экземпляров, реализующих тип интерфейс.

· Список полей интерфейса может включать только свойства и методы. Поля данных использовать нельзя. Соответственно, спецификаторы read и write должны быть методами.

· Все поля интерфейса всегда имеют тип видимости public (без явного указания); не разрешено использовать спецификаторов видимости (но у массивов может быть использован спецификатор default).

· Не бывает конструкторов и деструкторов.

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

· Наследование через интерфейсы множественное.

· Реализация интерфейса может быть только в классе, при этом он должен реализовать все методы интерфейса (а значит, и все свойства)

Интерфейс, как и класс, наследует все методы прародителя, однако только на уровне абстракций, без реализации методов. Однако интерфейс наследует право реализации этих методов в классе, поддерживающем этот интерфейс. В декларации интерфейса можно указать, что интерфейс наследуется от прародительского интерфейса. Если такого указания нет, то интерфейс является непосредственным потомком IUnknown, который определен в модуле System и является прародителем для всех интерфейсов. В IUnknown продекларировано 3 метода — QueryInterface, _AddRef, Release. QueryInterface предназначен для поддержки интерфейсов объектов. _AddRef, _Release обеспечивают управление ссылками на интерфейсы. Простейший путь реализовать эти методы — создать класс-наследник от tInterfacedObject , описанного в модуле System.

Пример описания интерфейса:

type

IMalloc =

interface(IUnknown)

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

function Alloc(Size: Integer): Pointer; stdcall;

functionRealloc(P:Pointer;Size:Integer):Pointer;stdcall;

procedure Free(P: Pointer); stdcall;

function GetSize(P: Pointer): Integer; stdcall;

function DidAlloc(P: Pointer): Integer; stdcall;

procedure HeapMinimize; stdcall;

end;

Перед использованием продекларированного интерфейса он должен быть реализован в классе. Реализация осуществляется с помощью декларации в списке прародителей класса:

type

className =

class (ancestorClass,interface1,...,interfaceN)

memberList

end;

Например,

type

TMemoryManager =

class(TInterfacedObject, IMalloc, IErrorInfo)

...

end;

Когда класс реализует интерфейс, он должен реализовать (или наследовать реализацию) каждого метода, декларированного в интерфейсе. Ниже приведено описание tInterfacedObject из модуля System:

type

TInterfacedObject =

class(TObject, IUnknown)

protected

FRefCount: Integer;

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

function _AddRef: Integer; stdcall;

function _Release: Integer; stdcall;

public

property RefCount: Integer read FRefCount;

end;

tInterfacedObject реализует интерфейс. Поэтому в нем описаны и реализованы все три метода IUnknown. Классы, реализующие интерфейсы, могут быть использованы как прародители. Класс tInterfacedObject реализует три метода интерфейса IUnknown и поэтому удобен как прародитель для всех классов, реализующих интерфейсы. Когда интерфейс реализован в классе, каждый из его методов реализован соответствующим методом (по умолчанию с тем же именем) и с такой же сигнатурой.

В классе-наследнике можно перекрыть методы реализуемого интерфейса. При этом соответствующий метод должен быть виртуальным или динамическим). Также возможно заново унаследовать интерфейс:

type

IWindow =

interface

['{00000115-0000-0000-C000-000000000146}']

procedure Draw;

...

end;

TWindow =

class(TInterfacedObject, IWindow) //TWindow реализует IWindow

procedure Draw;

...

end;

TFrameWindow =

class(TWindow, IWindow) //TFrameWindow переопределяет реализацию Iwindow

procedure Draw;

...

end;

При этом в классе все методы от прародительской реализации этого интерфейса запираются (не наследуются), в том числе выражения различения методов.

 

6.2. Реализация интерфейсов свойством

Директива implements в описании свойства класса позволяет делегировать реализацию интерфейса свойству в реализуемом классе. Например, свойство

property MyInterface: IMyInterface

read FMyInterface

implements IMyInterface;

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

· может иметь тип класса или интерфейса.

· не может быть массивом или иметь спецификатор индекса.

· должно иметь спецификатор read; если имеется метод “read”, он не может быть динамическим (хотя может быть виртуальным) или описывать директиву message.

· Если свойство типа интерфейс, соответствующий интерфейс или его интерфейс-прародитель должен присутствовать в списке интерфейсов класса, где продекларировано свойство.

Делегируемое свойство должно возвращать объект, чей класс полностью реализует интерфейс, без использования выражений различия методов (“мэппинга”), см. далее. Например:

type

IMyInterface =

interface

procedure P1;

procedure P2;

end;

TMyClass =

class(TObject, IMyInterface)

FMyInterface: IMyInterface;

property MyInterface: IMyInterface

read FMyInterface

implements IMyInterface;

end;

tatherClass=

class(tany,ImyInterface)

end;

var

aMyClass: TMyClass;

aOtherClass:tOtherClass;

aMyInterface: IMyInterface;

begin

aMyClass := TMyClass.Create;

aMyClass.FMyInterface := ... // объект, чей класс реализует ImyInterface

aMyInterfase:=aMyClass.myInterfase// ссылка на тот же объект

aMyInterface := MyClass;// допустимо, так как ImyInteface – интерфейс-прародитель для tMyClass; ссылка на объект,агрегирующий

aMyInterface.MyInterface:=totherClass.create;//это второй объект

aMyInterface.P1;

aMyInterfase.MyInterfase.P1;//это вызов методаP1 для второго объекта

end;

 

6.3. Различение имен при реализации нескольких интерфейсов

Когда класс реализует два или более интерфейсов, имеющих методы с одинаковыми именами, для разрешения конфликта имен используют выражения различения методов в виде “мэппинга”:

procedure interface.interfaceMethod = implementingMethod;

или

function interface.interfaceMethod = implementingMethod;

где implementingMethod — это метод, определенный в классе, или одном из его прародителей. Он может быть методом, описанном далее в описании класса, но не может быть “private” методом класса или одного из его прародителей, описанном в другом модуле. Пример:

type

TMemoryManager = class(TInterfacedObject, IMalloc, IErrorInfo)

function IMalloc.Alloc = Allocate;

procedure IMalloc.Free = Deallocate;

...

end;

Выражения различения методов не могут использовать мэппинг, имеющийся в классах-прародителях.

В спецификации интерфейса может быть описан уникальный глобальный идентификатор — globally unique identifier (GUID), представленный в виде строки литералов, заключенной в скобки:

['{xxxxxxxx–xxxx–xxxx–xxxx–xxxxxxxxxxxx}']

где каждый X это 16-ричная цифра ( от 0 до F). GUID — это 16-байтовое двоичное число, уникально идентифицирующее интерфейс.

Если у интерфейса есть GUID, можно его использовать для запросов к переменной интерфейса, получить ссылки на его реализации. Типы tGUID и pGUID, определенные в модуле System, используются для работы с переменными типа GUID и указателями на такие переменные. Их описание следующее:

type

pGUID = ^tGUID;

tGUID =

record

D1: Longword;

D2: Word;

D3: Word;

D4: array[0..7] of Byte;

end;

Можно описать типизированную константу типа TGUID, например, имеющую имя IID -IMalloc:

const IID_IMalloc: TGUID = '{00000002-0000-0000-C000-000000000046}';

Вызов в процедуре переменной типа GUID аналогичен соответствующему типу interfase. Может быть использовано как значение, так и постоянный параметр типа TGUID. Например:

function Supports(Unknown: IUnknown; const IID: TGUID): Boolean;

Таким образом, вызов Supports может быть сделан одним из двух путей:

if Supports(Allocator, IMalloc) then ...

или

if Supports(Allocator, IID_IMalloc) then ...

 

5.11. Спецификаторы вызовов процедур и функций

При задании процедуры или функции можно использовать спецификаторы вызова register, pascal, cdecl, stdcall, safecall. Например:

function MyFunction(X, Y: Real): Real; cdecl;

Эти спецификаторы влияют на особенности передачи параметров в процедуру, а также на работу со стеком, использование регистров, и обработку исключительных ситуаций. По умолчанию спецификатор считается register.

 

Директива Передача параметров Очищает стек Передает параметры Вызовы
Register слева направо программа через регистры по умолчанию, наиболее эфф.
Pascal слева направо программа через стек обратно совместимо с Turbo PASCAL
cdecl справа налево вызывающий через стек DLL,написанные на C или C++
stdcall справа налево программа через стек Windows API
safecall справа налево программа через стек Объекты COM; методы парных интерфейсов

5.12. Динамические массивы

Было:

var

A: array[1..100] of string;

B: array[1..10] of array [1..20] of integer;

Стало можно:

var

A: array of string;

B: array of array of integer;

Декларация динамического массива (переменных A и B) не выделяет под них памяти. Память выделяется процедурой SetLength:

SetLength(A,100);

SetLength(B,10,20);

Индексация динамических массивов всегда идет от нуля.

Для освобождения памяти из-под динамического массива:

а) либо присвоить nil переменной:

A:=nil;

B:=nil;

б) либо — вызвать процедуру Finalize:

Finalize(A);

Finalize(B);

Пример:

unit Unit1;

interface

uses

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,

StdCtrls;

type

TForm1 = class(TForm)

Button1: TButton;

Button2: TButton;

Label1: TLabel;

Label2: TLabel;

Button3: TButton;

Label3: TLabel;

Label4: TLabel;

Label5: TLabel;

Button4: TButton;

procedure Button1Click(Sender: TObject);

procedure Button2Click(Sender: TObject);

procedure Button3Click(Sender: TObject);

procedure Button4Click(Sender: TObject);

private

{ Private declarations }

public

{ Public declarations }

end;

var

Form1: TForm1;

A: array of string;

B: array of array of integer;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);

begin

close

end;

procedure TForm1.Button2Click(Sender: TObject);

begin

SetLength(A,100);

SetLength(B,10,20);

Label1.caption:='Память выделена';

end;

procedure TForm1.Button3Click(Sender: TObject);

var s:string;

begin

A[99]:='Ok';

B[9,19]:=7;

Label3.caption:=A[99];

str(B[9,19],s);

Label5.caption:=s;

end;

procedure TForm1.Button4Click(Sender: TObject);

begin

A:=nil;

Finalize(B);

Label1.caption:='Память освобождена';

end;

end.

 

5.13. Перезагрузка (overloading) методов, процедур и функций

Разрешено использование одинаковых имен, но с разными сигнатурами аргументов (числом или типом аргументов):

Constructor create(aOwner:tComponent);overload;override;

Constructor create(aOwner:tComponent;Text:String);overload;

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

Constructor create(aOwner:tComponent;Name:String);overload;

Пример для функций:

function Divide(x,y:real):Real;overload;

begin

Result:=x/y;

end;

function Divide(x,y:integer):Integer;overload;

begin

Result:=x div y;

end;

 

5.14. Параметры по умолчанию

Описываются аналогично константам в предыдущих версиях:

Name: Type=value;

При вызове параметры по умолчанию могут быть опущены. Например, если взять заголовок процедуры заполнения массива числом, передаваемым в качестве параметра:

Procedure FillArray(A:array of integer;V:integer=1);

Тогда вызов этой процедуры можно производить так:

FillArray(myArray,1);

Либо, что то же,

FillArray(myArray);

Будьте осторожны и не используйте вызовы overloaded процедур с опущенными значениями по умолчанию, когда число и тип параметров совпадают с одной из других overloaded процедур.