четверг, 1 марта 2012 г.

Классы и объекты

Перевод из справочной системы Delphi

Тип класс

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

  • Поле представляет собой переменную, которая является частью объекта. Как и поля записей, поля классов представляют элементы данных, которые присутствуют в каждом экземпляре класса.
  • Метод – это процедура или функция, связанная с классом. Большая часть методов оперирует объектами (т.е. экземплярами классов). Некоторые методы (называемые методами класса) работают с классами как с типами.
  • Свойство – это интерфейс к данным, связанным с объектом (которые часто хранятся в полях). Свойства имеют спецификаторы доступа, которые определяют, каким образом данные могут быть прочитаны или изменены. Из других частей программы, то есть снаружи объекта, свойство в большинстве случаев выглядит как поле.

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

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

SomeObject.Size := 100; 
//устанавливает значение свойства объекта SomeObject
Нет необходимости записывать эту операцию как:
SomeObject^.Size := 100;

Класс должен быть объявлен прежде, чем будет создан его экземпляр (вы не можете объявить класс внутри объявления переменной). Объявлять класс следует только во внешней области видимости программы или модуля, но не внутри объявления процедуры или функции. Объявление класса имеет вид:

type
   className = class [abstract | sealed] (ancestorClass)
      memberList
   end;
где className – любой допустимый идентификатор;

sealed и abstract – опциональные зарезервированные слова; Здесь синтаксис [abstract | sealed] обозначает, что может быть использовано только одно из зарезервированных слов. При этом значимыми являются непосредственно ключевые слова (скобки и символ "|" не должны присутствовать в объявлении).

Если класс помечен ключевым словом sealed, наследование от этого класса запрещается.

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

Замечание: Для обратной совместимости Delphi позволяет создавать экземпляры класса, объявленного как abstract, но пользоваться такой возможностью не следует.

Класс не может быть одновременно объявлен как abstract и sealed.

(ancestorClass) – Указание на класс-предок может не указываться; Если пропущено указание на класс-предок (ancestorClass), новый класс наследуется непосредственно от класса System.TObject. Если в объявление класса включается указание на класс-предок и список компонентов класса пуст, в объявлении можно не указывать ключевого слова end. Объявление класса может так же включать список интерфейсов, реализуемых классом.

memberList - объвляет компоненты класса (поля, свойства и методы);

Методы описываются в объявлении класса в виде заголовков, без указания тел. Программный код методов должен быть указан в других частях программы. Далее приведено объявление класса TMemoryStream из модуля Classes:

type 
TMemoryStream = class(TCustomMemoryStream)
  private
    FCapacity: Longint;
    procedure SetCapacity(NewCapacity: Longint);
  protected
    function Realloc(var NewCapacity: Longint): Pointer; 
                virtual;
    property Capacity: Longint read FCapacity 
                write SetCapacity;
  public
    destructor Destroy; override;
    procedure Clear;
    procedure LoadFromStream(Stream: TStream);
    procedure LoadFromFile(const FileName: string);
    procedure SetSize(NewSize: Longint); override;
    function Write(const Buffer; Count: Longint): Longint; 
                override;
  end;

Класс Classes.TMemoryStream происходит от Classes.TCustomMemoryStream, наследуя его методы. При этом он переопределяет несколько методов и свойств, включая деструктор. Конструктор Create наследуется без изменений от класса System.TObject (по этому он не указывается в объявлении). Каждый компонент класса, объявляется как private, protected, public (в объявлении нет методов, объявленных как published). Значение этих ключевых слов объясняется ниже.

После объявления класса можно создать его экземпляр:
var stream: TMemoryStream;
 stream := TMemoryStream.Create;

Наследование и область видимости

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

type TSomeControl = class(TControl);

объявляет класс с именем TSomeControl, наследуемый от Vcl.Controls.TControl. Класс автоматически наследует все компоненты класса-непосредственного предка. Каждый класс может объявить новые компоненты и переопределить наследуемые, но не может удалять компоненты, определенные в классе-предке. Следовательно TSomeControl содержит все компоненты, определенных в Vcl.Controls.TControl и его предках.

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


TObject и TClass

Класс System.TObject, объявленный в модуле System, является обязательным предком всех прочих классов. System.TObject определяет лишь несколько методов, включая конструктор и деструктор. Вдобавок к System.TObject, модуль System объявляет ссылку на тип класса System.TClass:

TClass = class of TObject;

Если при объявлении класса опускается предок, класс наследуется напрямую от System.TObject. То есть:

type TMyClass = class
      ...
     end;
эквивалентно:
type TMyClass = class(TObject)
      ...
     end;

Последняя форма является более предпочтительной, поскольку облегчает чтение кода.


Совместимость типов классов

Тип класса совместим по присваиванию с предками. Из этого следует, что переменная типа класс может ссылаться на экземпляр любого класса предка. Например, объявив:

type
  TFigure = class(TObject);
  TRectangle = class(TFigure);
  TSquare = class(TRectangle);
var
 Fig: TFigure;

Переменной Fig можно присвоить значения типа TFigure, TRectangle или TSquare.


Объектный тип

Компилятор Delphi Win32 разрешает альтернативный сиснаксис объявления типа класса. Вы можете объявить объектный тип, используя следующий синтаксис:

type ObjectTypeName = object (AncestorObjectType)
        memberList
     end;

где ObjectTypeName это любой допустимый идентификатор, указание предка (AncestorObjectType) является опциональным, а memberList объявляет поля, методы и свойства. Если (ancestorObjectType) опущен в объявлении – новый тип не имеет предка. Объектный тип не может иметь членов с видимостью published.

Если объектный тип не наследуется от System.TObject, он не предоставляет встроенных конструкторов, деструкторов или других методов. Вы можете создаваться экземпляры объктного типа при помощи процедуры New, разрушать их процедурой Dispose или просто объявлять переменные объектного типа, также, как это происходит с записями.

Объектные типы поддерживаются только для обратной совместимости. Их использование не рекомендовано на платформе Win32.


Видимость компонентов класса

Каждый компонент класса имеет атрибут, называемый видимостью, который определяется одним из зарезервированных слов private, protected, public, published или automated. Например:

published property Color: TColor read GetColor 
                write SetColor;

объявляет свойство Color с областью видимости published. Видимость определяет, где и как будет доступен компонент класса. Наименьшая доступность у видимости private, protected имеет среднюю доступность, а видимость public, published и automated имеют самую большую доступность.

Если в объявлении компонента класса опускается спецификатор видимости, компонент имеет такую же видимость, как у компонента, объявленного перед ним. Компоненты без спецификаторов видимости, следующие в начале объявления класса, по умолчанию получают видимость published (если у компилятора установлен режим {$M+}, или если класс, от которого наследуется объявляемый класс, скомпилирован в режиме {$M+}) или public (во всех остальных случаях).

Для удобства чтения кода, наилучшим решением будет организовать объявление класса с учетом видимости его компонентов: в начало объявления помещать компоненты с видимостью private , затем – компоненты с видимостью protected и так далее. В этом случае зарезервированное слово для обозначения видимости указывается однократно в начале "секции". Таким образом, типичное объявление класса выглядит:

type
  TMyClass = class(TControl)
    private
      { private declarations here }
    protected
      { protected declarations here }
    public
      { public declarations here }
    published
      { published declarations here }
  end;

Переопределяя класс-наследник, вы можете увеличить видимость его компонентов, но не снизить ее. Например, свойство, помеченное как protected в классе-предке, может быть объявлено как public в классе-наследнике, не может быть объявлено как private. Более того, компоненты с видимостью published не могут получить видимость public в классе-наследнике.


Компоненты с видимостью private, protected и public

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

Размещая связанные объявления классов в одном и том же модуле, вы можете предоставить классам доступ к чужим компонентам с видимостью private, но при этом все таки ограничиваете доступ к ним.

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

Компоненты с видимостью public видимы везде, где доступен их класс.


Спецификаторы строгой видимости

В дополнение к спецификаторам private и protected, компилятор Delphi поддерживает настройки видимости с более строгими ограничениями. Это спецификаторы видимости strict private и strict protected. Эти настройки могут быть использованы в приложениях на платформе Win32.

Компоненты классов с видимостью strict private доступны только внутри класса, в котором они объявлены. Они не видны другим процедурам или функциям, объявленным внутри модуля, в котором объявлен этот класс. Компоненты класса с видимостью strict protected видимы внутри класса, в котором они объявлены и внутри всех классов-наследников, вне зависимости от того, где объявлены они. Более того, когда компоненты экземпляра класса (объявление без использования ключевых слов class или class var) объявлены strict private или strict protected, они недоступны снаружи экземпляра класса, в котором они объявлены. Экземпляр класса не может получить доступ к компонентам с видимостью strict protected или strict protected, относящимся к другому экземпляру этого же класса.

Традиционные в Delphi спецификаторы видимости private совпадают с видимостью assembly в CLR. Спецификатор видимости protected совпадает либо с видимостью Family or Assembly в CLR.

Замечание: Ключевое слово strict обрабатывается как директива в контексте объявления класса. Внутри объявления класса вы не можете объявить метод с именем "strict", но это возможно вне объявления класса.

Компоненты с видимостью published

Компоненты с видимостью published имеют такую же видимость, как и компоненты, помеченные как public. Различие между ними заключается в том, что информация о типах в режиме выполнения программы (run-time type information - RTTI) создается для компонентов с видимостью published. RTTI позволяет приложению динамически получать информацию о полях, свойствах и методах класса. RTTI используется для получения доступа к значениям свойств при загрузке и сохранении файлов, отображения свойств объекта в Инспекторе Объектов и связи специальных методов (называемых обработчиками событий) со свойствами-событиями.

Компоненты с видимостью Published имеют ограничения по типам данных. Видимость published могут иметь компоненты порядковых, строковых, интерфейсных, вариантных типов, типа класс и указатели на метод. Видимость published могут также иметь компоненты типа множество, для которых указываются верхняя и нижняя границы базового типа с порядковыми значениями от 0 до 31 (другими словами, множество должно помещаться в байт, слово или двойное слово). Кроме того, "опубликован" может быть любой вещественный тип (кроме Real48). Свойства типа массив (array type), в отличии от свойств-массивов (array properties), не могут иметь видимость published.

Некоторые свойства хоть и могут иметь видимость published, неполностью поддерживаются потоковой системой. Это относится к типам запись, свойствам-массивам любых "публикуемых" типов и свойствам перечисляемых типов, имеющих анонимные значения. Если вы устанавливаете видимость published для таких свойств, Инспектор Объектов не будет корректно отображать их, а значение свойства не будет сохраняться при сохранении объектов на диск.

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

Класс не может иметь компонентов с видимостью published за исключением тех случаев, когда он компилируется с ключом компилятора {$M+} или наследуется от класса, скомпилированного в режиме {$M+}. Большинство классов, имеющих компоненты с видимостью published, наследуются Classes.TPersistent, который скомпилирован с ключом {$M+}. Таким образом, необходимости в частом использовании директивы $M нет.

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

Компоненты с видимостью automated (только платформа Win32)

Компоненты с видимостью automated имеют ту же видимость, что и компоненты с видимостью public. Отличие заключается в том, что для компонентов с видимостью automated генерируется информация типа Automation (Automation type information), которая требуется для серверов Automation. Компоненты с видимостью automated обычно встречаются только в классах Win32. Зарезервированное слово automated поддерживается для обратной совместимости. Класс TAutoObject в модуле ComObj не использует automated.

Для методов и свойств, объявленных как automated действуют следующие ограничения:

  • Типы всех свойств, параметры свойств-массивов, параметры методов и значения, возвращаемые функциями должны быть автоматизируемы. К автоматизируемым типам относятся: Byte, Currency, Real, Double,Longint, Integer, Single, Smallint, AnsiString, WideString, TDateTime, Variant, OleVariant, WordBool и все интерфейсные типы.
  • Объявления методов должны использовать конвенцию вызова по умолчанию register. Они могут быть виртуальными, но не динамическими.
  • Объявление свойств может включать спецификаторы доступа (read и write), но другие спецификаторы (index, stored, default и nodefault) запрещены. Спецификаторы доступа должны указывать на идентификаторы методов, использующих конвенцию вызова по умолчанию register. Идентификаторы полей в этом случае запрещены.
  • Объявления свойств должны указывать тип. Перекрытие свойств запрещено.

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

На платформе Win32 за этой директивой должна следовать целочисленная константа, которая определяет диспетчерский идентификатор Automation (Automation dispatch ID) для члена класса. В противном случае компилятор автоматически назначает компоненту диспетчерский идентификатор, который больше чем наибольший, встречающийся у методов и свойств в классе и его предках.


Упреждающие объявления и взаимозависимые классы

Если объявление класса заканчивается словом класс и точкой с запятой (;), то есть имеет вид:

type  className = class;

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

Упреждающие объявления позволяют создавать взаимозависимые классы. Например:

type
  TFigure = class;   // упреждающее объявление
  TDrawing = class
    Figure: TFigure;
    // ...
  end;
 
  TFigure = class   // определяющее объявление
    Drawing: TDrawing;
    // ...
  end;

Не стоит путать упреждающие объявления с полными объявлениями классов, наследуемых от System.TObject и не объявляющих дополнительных членов.

type
  TFirstClass = class;   // это упреждающее объявление
  TSecondClass = class   // это полное объявление класса
  end;                   //
  TThirdClass = class(TObject);  
                         // это полное объявление класса

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