Delphi World - Создание и использование интерфейса
Delphi World - это проект, являющийся сборником статей и малодокументированных возможностей  по программированию в среде Delphi. Здесь вы найдёте работы по следующим категориям: delphi, delfi, borland, bds, дельфи, делфи, дэльфи, дэлфи, programming, example, программирование, исходные коды, code, исходники, source, sources, сорцы, сорсы, soft, programs, программы, and, how, delphiworld, базы данных, графика, игры, интернет, сети, компоненты, классы, мультимедиа, ос, железо, программа, интерфейс, рабочий стол, синтаксис, технологии, файловая система...
Создание и использование интерфейса

Интерфейсы играют главную роль в технологиях СОМ (Component Object Model - компонентная модель объектов), CORBA (Common Object Request Broker Architecture - архитектура с брокером требуемых общих объектов) и связанных с ними технологиях удаленного доступа, т. е. технологиях доступа к объектам, расположенным (и выполняющимся) на другой машине. Их основная задача - описать свойства, методы и события удаленного объекта в терминах машины клиента, т. е. на используемом при разработке клиентского приложения языке программирования. С помощью интерфейсов программа клиента обращается к удаленному объекту так, как если бы он был ее собственным объектом.

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

Интерфейсы представляют собой частный случай описания типов. Они объявляются с помощью зарезервированного слова interface. Например:

type
  IEdit = interface
    procedure Copy; stdcall;
    procedure Cut; stdcall;
    procedure Paste; stdcall;
    function Undo: Boolean; stdcall;
  end;

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

В отличие от классов интерфейс не может содержать поля, и, следовательно, объявляемые в нем свойства в разделах read и write могут ссылаться только на методы. Все объявляемые в интерфейсе члены размещаются в единственной секции public. Методы не могут быть абстрактными (abstract), виртуальными (virtual), динамическими (dynamic) или перекрываемыми (override). Интерфейсы не могут иметь конструкторов или деструкторов, т. к. описываемые в них методы реализуются только в рамках поддерживающих их классов, которые называются интерфейсными.

Если какой-либо класс поддерживает интерфейс (т. е. является интерфейсным), имя этого интерфейса указывается при объявлении класса в списке его родителей:

TEditor = class(TInterfacedObject, IEdit)
  procedure Copy; stdcall;
  procedure Cut; stdcall;
  procedure Paste; stdcall;
  function Undo: Boolean; stdcall;
end;

В отличие от обычного класса интерфейсный класс может иметь более одного родительского интерфейса:

type
  IMylnterface = interface procedure Delete; stdcall;
  end;
  TMyEditor = class(TInterfacedObiect, lEdit, IMylnterface)
    procedure Copy; stdcall;
    procedure Cut; stdcall;
    procedure Paste; stdcall;
    function Undo: , Boolean; stdcall;
    procedure Delete; stdcall;
  end;

В любом случае в разделе реализации интерфейсного класса необходимо описать соответствующие интерфейсные методы. Если, например, объявлен интерфейс

IPaint = interface
  procedure CirclePaint(Canva: TCanvas; X, Y, R: Integer);
  procedure RectPaint(Canva: TCanvas; X1, Y1, X2, Y2: Integer);
end;

и использующий его интерфейсный класс

TPainter = class(TInterfacedObject, IPaint)
  procedure CirclePaint(Canva: TCanvas; X, Y, R: Integers);
  procedure RectPaint(Canva: TCanvas; X1, Y1, X2, Y2: Integer);
end;

то в разделе implementation следует указать реализацию методов:

procedure TPainter.CirclePaint(Canva: TCanvas; X, Y, R: Integers;
begin
  with Canva do
    Ellipse(X, Y, X + 2 * R, Y + 2 * R);
end;

procedure TPainter.RectPaint(Canva: TCanvas; X1, Y1, X2, Y2: Integer);
begin
  with Canva do
    Rectangle(XI, Yl, X2, Y2)
end;

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

procedure TForm1.PaintBoxIPaint(Sender: TObject);
var
  Painter: IPaint;
begin
  Painter := TPainter.Create;
  Painter.CirclePaint(PaintBoxl.Canvas, 10, 0, 10);
  Painter.RectPaint(PaintBoxl.Canvas, 40, 0, 60, 20);
end;

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

type
  TPainter = class(TInterfacedObject, IPaint)
  end;

было бы ошибкой: компилятор потребовал бы вставить описание методов CirclePaint и RectPaint.

Подобно тому как все классы в Object Pascal порождены от единственного родителя TObject, все интерфейсные классы порождены от общего предка TInterfacedObject. Этот предок умеет распределять память для интерфейсных объектов и использует глобальный интерфейс lunknow:

type
  TInterfacedObject = class(TObject, lUnknown)private
    FRefCount: Integer;
  protected
    function Querylnterface(
      const IID: TGUID; out Obj): Integer; stdcall;
    function _AddRef: Integer; stdcall;
    function _Release: Integer; stdcall;
  public
    property RefCount: Integer read FRefCount;
  end;

Если бы в предыдущем примере класс TPainter был описан так:

TPainter = class(IPaint)
  procedure CirclePaint(Canva: TCanvas; X, Y, R: Integer);
  procedure RectPaint(Canva: TCanvas; X1, Y1, X2, Y2: Integer);
end;

компилятор потребовал бы описать недостающие методы Queryinterface, _Add И _Release класса TInterfacedObject. Поле FRef Count этого класса служит счетчиком вызовов интерфейсного объекта и используется по принятой в Windows схеме: при каждом обращении к методу Add интерфейса IUnknow счетчик наращивается на единицу, при каждом обращении к Release - на единицу сбрасывается. Когда значение этого поля становится равно 0, интерфейсный объект уничтожается и освобождается занимаемая им память.

Если интерфейс предполагается использовать в технологиях COM/DCOM или CORBA, его методы должны описывать с директивой stdcall или (для объектов Автоматизации) safecall

К интерфейсному объекту можно применить оператор приведения типов as, чтобы использовать нужный интерфейс:

procedure PaintObjects(P: TInterfacedObject)
var
  X: IPaint;
begin
  try
    X := P as IPaint;
    X.CirclePaint(PaintBoxl.Canvas, 0, 0, 20)
  except
    ShowMessage('Объект не поддерживает интерфейс IPaint')
  end
end;

Встретив такое присваивание, компилятор создаст код, с помощью которого вызывается метод Queryinterface интерфейса IUnknow с требованием вернуть ссылку на интерфейс IPaint. Если объект не поддерживает указанный интерфейс, возникает исключительная ситуация.

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

IPaint = interface
  ['{A4AFEB60-7705-11D2-8B41-444553540000}']
  procedure CirclePaint(Canva: TCanvas; X, Y, R: Integer);
  procedure RectPaint(Canva: TCanvas; Xl, Yl, X2, Y2: Integer);
end;

Глобально-уникальные идентификаторы создаются по специальной технологии, гарантирующей ничтожно малую вероятность того, что два guid совпадут. Эта технология включена в Windows 32: чтобы получить guid для вновь созданного интерфейса в среде Delphi, достаточно нажать клавиши Ctrl+Shift+G. Для работы с guid в модуле System объявлены следующие типы:

type
  PGUID = ^TGUID;
  TGUID = record Dl: LongWord;
    D2: Word;
    D3: Word;
    D4: array[0..7] of Byte;
  end;

Программист может объявлять типизированные константы типа tguid, например:

const IID_IPaint: TGUID= ['{A4AFEB61-7705-11D2-8B41-444553540000}'] ; 

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

procedure Paint(const IID: TGUID); 

Paint(IPaint) ; 
Paint(IID_Paint); 

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

type
  IMylnterface = interface procedure P1; procedure P2;
  end;
  TMyClass = class(TObject, IMylnterface)
    FMyInterface: IMylnterface;
    property Mylnterface: IMylnterface
      read FMyInterface implements IMylnterface;
  end;

Обратите внимание: в этом примере класс TMyciass не является интерфейсным, т. е. классом, в котором исполняются методы p1 и P2. Однако если из него убрать определение уполномоченного свойства Mylnterface, он станет интерфейсным, и в нем должны быть описаны методы интерфейса IMylnterface.

Уполномоченное свойство обязательно должно иметь часть read. Если оно имеет тип класса, класс, в котором оно объявлено, не может иметь других уполномоченных свойств.

Проект Delphi World © Выпуск 2002 - 2017
Автор проекта: Эксклюзивные курсы программирования