среда, 2 мая 2012 г.

Методы

Перевод из справочной системы Delphi. Оригинал:Methods

Метод – это процедура или функция, связанная с классом. При вызове метода определяется объект (или, если это метод класса, то класс) с которым должен работать метод. Например, SomeObject.Free вызывает метод Free объекта SomeObject.


О методах

Внутри объявления класса методы представлены заголовками процедур или функций, которые работают как упреждающие объявления. Где-то после объявления класса (но в этом же модуле) каждый метод должен быть реализован добавлением определяющего объявления. Например, предположим, что объявление TMyClass включает метод с именем DoSomething:

 type
    TMyClass = class(TObject)
       ...
       procedure DoSomething;
       ...
    end;

Определяющее объявление для DoSomething должно быть включено ниже в этом же модуле:

 procedure TMyClass.DoSomething;
 begin
      ...
 end;

Класс может быть объявлен в модуле как в секции interface, так и в секции implementation, но определяющие объявления методов должны быть расположены в секции implementation.

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

Объявления методов могут включать специальные директивы, которые не используются при объявлении прочих процедур и функций. Директивы должны указываться только в объявлении класса, но не в определяющем объявлении, и могут следовать только в следующем порядке: reintroduce; overload; binding; calling convention; abstract; warning, где:

  • binding может принимать значения: virtual, dynamic или override;
  • calling convention может принимать значения: register, pascal, cdecl, stdcall или safecall;
  • warning может принимать значения: platform, deprecated или library.

Ключевое слово Inherited

Ключевое слово inherited играет особую роль при реализации полиморфизма в поведении методов. Оно может встречаться в объявлении методов, причем иногда с указанимем идентификатора после него, а иногда – без.

Если за ключевым словом inherited следует имя компонента, оно обозначает обычный вызов метода, обращение к свойству или полю, за исключением того, что поиск компонента, к которому идет обращение, начинается с непосредственного предка класса, к которому относится компонент. Например, если в определяющем объявлении метода присутствует:

inherited Create(...);

происходит вызов наследуемого конструктора Create.

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

inherited;

встречается часто в реализации конструкторов. Вызывает наследуемый конструктор с таким же списком параметров (передаваемых наследуемому конструктору).


Идентификатор Self

При реализации метода идентификатор Self обращается к объекту, из которого происходит вызов метода. Для примера приведем реализацию метода Add класса TCollection, объявленного в модуле Classes:

 function TCollection.Add: TCollectionItem;
 begin
     Result := FItemClass.Create(Self);
 end;

Метод Add вызывает метод Create класса, на который ссылается поле FitemClass (который всегда является наследником TCollectionItem). TCollectionItem.Create принимает единственный параметр типа TСollection. Таким образом, метод Add передает экземпляр объекта типа TCollection, вызывающего его. Это аналогично коду:

 var MyCollection: TCollection;
     ...
     MyCollection.Add   // MyCollection передается в 
                        // метод TCollectionItem.Create

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


Связывание методов

Связывание методов может быть статическим (static) (по умолчанию), виртуальным (virtual) или динамическим (dynamic). Виртуальные и динамические методы могут перекрываться и они могут быть абстрактными. Это подразделение методов начинает иметь значение, когда переменная одного типа класса хранит значение типа класса-потомка. Оно определяет, какая реализация метода будет активирована при запуске метода.


Статические методы

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

 type
     TFigure = class
       procedure Draw;
     end;
 
     TRectangle = class(TFigure)
       procedure Draw;
     end;

При таком объявлении приведенный ниже код иллюстрирует вызов статического метода. Во втором вызове Figure.Draw, переменная Figure ссылается на класс TRectangle, но вызов метода активирует реализацию метода Draw в классе TFigure, поскольку объявленный тип переменной Figure – это TFigure:

 var
     Figure: TFigure;
     Rectangle: TRectangle;
 
     begin
             Figure := TFigure.Create;
             Figure.Draw;              // calls TFigure.Draw
             Figure.Destroy;
             Figure := TRectangle.Create;
             Figure.Draw;              // calls TFigure.Draw
 
             TRectangle(Figure).Draw;  // calls TRectangle.Draw
 
             Figure.Destroy;
             Rectangle := TRectangle.Create;
             Rectangle.Draw;          // calls TRectangle.Draw
             Rectangle.Destroy;
     end;

Виртуальные и динамические методы

Для того, чтобы сделать метод виртуальным или динамическим необходимо включить в его объявление соответствующую директиву (virtual или dynamic). Виртуальные и динамические методы, в отличии от статических могут быть перекрыты в классе-предке. При вызове перекрытого метода, решение о том, какую реализацию метода активировать, принимает действительный (определенный в процессе работы программы) тип класса или объекта, использванный при вызове метода.

Чтобы перекрыть метод его следует объявить повторно с указанием директивы override. Перекрывающее объявление должно совпадать с объявлением в классе-предке по порядку и типу параметров, а так же типу возвращаемого результата (для функций). В следующем примере метод Draw, объявленный в классе TFigure перекрывается в двух классах предках:

 type
     TFigure = class
       procedure Draw; virtual;
     end;
 
     TRectangle = class(TFigure)
       procedure Draw; override;
     end;
 
     TEllipse = class(TFigure)
       procedure Draw; override;
     end;

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

 var
    Figure: TFigure;
 
    begin
      Figure := TRectangle.Create;
      Figure.Draw;      // calls TRectangle.Draw
      Figure.Destroy;
      Figure := TEllipse.Create;
      Figure.Draw;      // calls TEllipse.Draw
      Figure.Destroy;
    end;

Только виртуальные и динамические методы могут быть перекрыты. Тем не менее, любые методы могут быть перегружены. Компилятор Delphi так же поддерживает концепцию финального виртуального метода. Если при объявлении виртуального метода было использовано ключевое слово final, классы-предки не смогут перекрывать его. Применение ключевого слова final может быть хорошим инженерным решением, которое может помочь документировать планируемое применение класса. Кроме того, оно помогает компилятору оптимизировать код.


Виртуальный или динамический?

В Delphi для Win32, виртуальные и динамические методы семантически одинаковы. Тем не менее, они отличаются в реализации обработки вызовов методов при выполнении программы. Виртуальные методы оптимизированы с точки зрения скорости, а динамические - снижают размер результирующего кода.

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

Замечание: Динамические методы следует использовать только в тех случаях, когда выгода от их применения очевидна. Во всех обычных ситуациях следует использовать виртуальные методы.
Скрывать или перекрывать?

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

 type
    T1 = class(TObject)
       procedure Act; virtual;
    end;
 
    T2 = class(T1)
       procedure Act;   // Act is redeclared, but not overridden
    end;
 
 var
    SomeObject: T1;
 
 begin
    SomeObject := T2.Create;
    SomeObject.Act;    // calls T1.Act
 end;

Директива Reintroduce

Директива reintroduce подавляет предупреждения компилятора о сокрытии объявленных ранее виртуальных методов. Например:

 procedure DoSomething; reintroduce; // класс-предок также
                                     // имеет метод DoSomething

Директиву reintroduce следует использовать, когда вы хотите скрыть наследуемый виртуальный метод, заменив его новым методом.


Абстрактные методы

Абстрактные методы – это виртуальные или динамические методы, не имеющие реализации в том классе, где они объявлены. Они должны быть реализованы в классе-потомке. Абстрактные методы должны объявляться с использованием директивы abstract, следующей за директивой virtual или dynamic. Например:

 procedure DoSomething; virtual; abstract;

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


Методы класса

Большая часть методов называются методами экземпляров, поскольку они работают с отдельными экземплярами объекта. Метод класса – это метод (отличный от конструктора), который оперирует классами, но не объектами. Существует два типа методов класса: обычные и статические.


Обычные методы класса

Объявление метода класса должно начинаться с зарезервированного слова class. Например:

 type
   TFigure = class
   public
      class function Supports(Operation: string): Boolean; virtual;
      class procedure GetInfo(var Info: TFigureInfo); virtual;
      ...
   end;

Определяющее объявление метода класса также начинается с зарезервированного слова class:

 class procedure TFigure.GetInfo(var Info: TFigureInfo);
 begin
     ...
 end;

В определяющем объявлении метода класса идентификатор Self представляет класс, в котором вызывается метод (может быть классом-потомком, по отношению к классу, в котором объявлен метод). Если методы вызывается в классе С, Self имеет тип class of C.

Таким образом, вы не сможете использовать Self для доступа к полям, свойствам или обычным методам экземпляра. Вы можете использовать Self для вызова конструкторов, прочих методов класса или для доступа к свойствам или полям класса. Метод класса может быть вызван при помощи ссылки на класс или объект. При вызове через ссылку на объект, класс объекта принимает значение Self.


Статические методы класса

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

Для объявления статического метода класса следует добавить ключевое слово класса к объявлению:

 type
    TMyClass = class
      strict private
        class var
          FX: Integer;
 
      strict protected
        // Замечание: компоненты, получающие доступ к свойствам класса
        // должны быть объявлены как class static.
        class function GetX: Integer; static;
        class procedure SetX(val: Integer); static;
 
      public
        class property X: Integer read GetX write SetX;
        class procedure StatProc(s: String); static;
    end;

Как и обычные методы класса, статические методы могут вызываться при помощи типа класса (без ссылки на экземпляр класса):

 TMyClass.X := 17;
 TMyClass.StatProc('Hello');

Перегрузка методов

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

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

 type
   T1 = class(TObject)
     procedure Test(I: Integer); overload; virtual;
   end;
 
   T2 = class(T1)
     procedure Test(S: string); reintroduce; overload;
   end;
   ...
 
 SomeObject := T2.Create;
 SomeObject.Test('Hello!');       // вызывает T2.Test
 SomeObject.Test(7);              // вызывает T1.Test

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

 type
     TSomeClass = class
       published
         function Func(P: Integer): Integer;
         function Func(P: Boolean): Integer;   // ошибка
           ...

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


Конструкторы

Конструктор – это особый метод, который создает и инициализирует объекты. Объявление конструктора выглядит как объявление обычной процедуры, но начинается с ключевого слова constructor. Примеры:

 constructor Create;
 constructor Create(AOwner: TComponent);

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

Класс, может иметь более, чем один конструктор, но большая часть классов имеет только один. Обычно для конструкторов используется имя Create.

Для создания объекта следует вызывать конструктор, специфицируя название типа класса. Например:

 MyObject := TMyClass.Create;

Эта инструкция выделяет память для нового объекта, устанавливает значения для всех целочисленных полей в 0, присваивает значение nil для всех полей указательного типа и устанавливает пустое значение для всех строковых полей. Прочие операции, указанные в реализации конструктора, выполняются позже. Обычно объекты инициализируются значениями, которые передаются как параметры в конструктор. Наконец, конструктор возвращает ссылку на только что созданный и инициализированный объект. Тип возвращаемого значения совпадает с типом, указанным при вызове конструктора.

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

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

Далее приведен пример класса и его конструктора:

  type
    TShape = class(TGraphicControl)
      private
        FPen: TPen;
        FBrush: TBrush;
        procedure PenChanged(Sender: TObject);
        procedure BrushChanged(Sender: TObject);
      public
        constructor Create(Owner: TComponent); override;
        destructor Destroy; override;
        ...
    end;
 
 constructor TShape.Create(Owner: TComponent);
 begin
     inherited Create(Owner);     // Initialize inherited parts
     Width := 65;          // Change inherited properties
     Height := 65;
     FPen := TPen.Create;  // Initialize new fields
     FPen.OnChange := PenChanged;
     FBrush := TBrush.Create;
     FBrush.OnChange := BrushChanged;
 end;

Первой инструкцией в конструкторе обычно бывает вызов наследуемого конструктора для инициализации унаследованных полей. Затем конструктор инициализирует поля, объявленные в классе-потомке. Поскольку конструктор всегда очищает память, которую он выделяет для создания объекта, все поля порядковых типов принимают значение 0, указатели и переменные типа класс – nil, строковые типы инициализируются пустым значением, а вариантные типы – значением Unassigned. Таким образом нет необходимости в теле конструктора инициализировать поля значениями, отличными от ноля или пустых значений.

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


Деструкторы

Деструктор – это особый метод, который при вызове разрушает объект и освобождает занимаемую им память. Объявление деструктора выглядит как объявление обычной процедуры, но начинается с ключевого слова destructor. Например:

 destructor SpecialDestructor(SaveData: Boolean);
 destructor Destroy; override;

Деструкторы на платформе Win32 используют по умолчанию конвенцию вызова register. Хоть класс и может иметь несколько деструкторов, рекомендуется перекрывать наследуемый метод Destroy, без объявления прочих деструкторов.

Для вызова деструктора вы должны специфицировать экземпляр объекта:

MyObject.Destroy;

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

Далее приведен пример реализации деструктора:

destructor TShape.Destroy;
 begin
     FBrush.Free;
     FPen.Free;
     inherited Destroy;
 end;

Последней инструкцией в теле деструктора обычно бывает вызов наследуемого деструктора, которые уничтожает наследуемые поля. При возникновении исключительной ситуации в процессе создании объекта, для разрушения незаконченного объекта автоматически вызывается деструктор Destroy. Это обозначает, что деструктор Destroy должен быть подготовлен к разрушению частично созданных объектов. Поскольку конструктор устанавливает нулевые и пустые значения полей перед выполнением каких-либо других действий, поля типа класс и указатели в частично созданном объекте всегда имеют значение nil. Прежде чем работать с такими полями деструктор должен проверять их на наличие этого значения. Вызов метода Free (объявлен в TObject) – это более подходящий способ для разрушения объекта с предварительной проверкой значений его полей.


Конструкторы классов

Конструктор класса – это особый метод класса, который недосупен разработчикам. Вызовы конструкторов автоматически вставляются компилятором в код секции инициализации модуля, в котором объявлен класс. Обычно конструкторы классов применяются для инициализации статических полей класса или для выполнения особого типа инициализации, требуемой для того, чтобы класс или какой-либо его экземпляр функционировал корректно. Даже если такой же результат может быть достигнут размещением кода инициализации класса в секции initialization, конструкторы класса в большей степени помогают компилятору решить, какой именно класс должен быть включен в результирующий двоичный файл, а какой – удален из него.

Следующий пример показывает обычную инициализацию полей класса:

 type
   TBox = class
   private
     class var FList: TList;
   end;
 
 implementation
 
 initialization
   { Initialize the static FList member }
   TBox.FList := TList.Create();
 
 end.

Этот способ плох тем, что приложение включает модуль, объявляющий TBox, даже в том случае, если этот модуль на самом деле не используется им. В приведенном примере класс TBox включается в результирующий бинарный файл, поскольку он упоминается в секции инициализации. Чтобы исправить ситуацию, рассмотрим применение конструкторов класса:

type
   TBox = class
   private
     class var FList: TList;
     class constructor Create;
   end;
 
 implementation
 
 class constructor TBox.Create;
 begin
   { Initialize the static FList member }
   FList := TList.Create();
 end;
 
 end.

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

Замечание: Несмотря на то, что компилятор уделяет внимание порядку инициализации классов, в некоторых сложных ситуациях он может оказаться случайным. Это происходит, когда конструктор класса зависит от состояния другого класса, который, в свою очередь, зависит от первого.
Замечание: конструктор класса-дженерика или записи может быть выполнен несколько раз. Точное количество запусков конструктора зависит от количества специализированных версий типа-дженерика. Например, конструктор класса для специализированного класса TList<String> может быть выполнен несколько раз в одном и том же приложении.

Деструкторы классов

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

type
   TBox = class
   private
     class var FList: TList;
     class constructor Create;
     class destructor Destroy;
   end;
 
 implementation
 
 class constructor TBox.Create;
 begin
   { Initialize the static FList member }
   FList := TList.Create();
 end;
 
 class destructor TBox.Destroy;
 begin
   { Finalize the static FList member }
   FList.Free;
 end;
 
 end.
Замечание: деструктор класса-дженерика или записи может быть выполнен несколько раз. Точное количество запусков деструктора зависит от количества специализированных версий типа-дженерика. Например, деструктор класса для специализированного класса TList<String> может быть выполнен несколько раз в одном и том же приложении.

Методы-обработчики сообщений

Обработчики сообщений реализуют реакцию на динамически передаваемые сообщения. Синтаксис метода-обработчика сообщения поддерживается для всех платформ. Приложения VCL применяют обработчики сообщений для реагирования на сообщения Windows.

Обработчик сообщения объявляется при помощи директивы message, за которой следует целочисленная константа с диапазоном значений от 1 до 49151, представляющая собой идентификатор сообщения. Для обработчиков сообщений в элементах управления VCL эта константа может быть идентификатором одного из сообщений Win32, определенных в модуле Messages. Метод-обработчик сообщения должен быть процедурой с единственным параметром, передаваемым по ссылке.

Пример:

 type
     TTextBox = class(TCustomControl)
       private
        procedure WMChar(var Message: TWMChar); message WM_CHAR;
        ...
     end;

Обработчик сообщения не должен содержать директивы override для переопределения наследуемого метода. На самом деле он даже не должен повторять имени или типа параметра перекрываемого метода. Только идентификатор сообщения определяет, какое сообщение должен обработать метод и является ли он перекрывающим методом.


Реализация методов-обработчиков сообщений

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

 procedure TTextBox.WMChar(var Message: TWMChar);
 begin
    if Message.CharCode = Ord(#13) then
       ProcessEnter
    else
       inherited;
 end;

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

Реализация DefaultHandler в TObject не подразумевает выполнения каких-либо операций. Перекрывая DefaultHandler, класс может реализовать собственную обработку сообщений по умолчанию. Для элементов управления на платформе Win32, метод DefaultHandler вызывает Win32 API DefWindowProc.


Обработка сообщений

Обработчики сообщений редко вызываются явно. Обычно сообщения передаются объекту при помощи метода Dispatch , наследуемого от TObject:

 procedure Dispatch(var Message);
 

Параметр Message передаваемый методу Dispatch должен быть записью, первым полем которой должно быть поле типа Word, содержащее идентификатор сообщения.

Dispatch выполняет поиск обработчика сообщения в иерархии классов (начиная с класса объекта, в котором был вызван этот метод) и запускает первый найденный обработчик для идентификатора, который был ему передан. Если обработчика для сообщения с данным идентификатором не находится, Dispatch вызывает DefaultHandler.

2 комментария: