Составляющие класса

Поля

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

typeTMyClass = class

aIntField: Integer;

aStrField: String;

aObjField: TObject;

end;

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

Type

TMyClass = class

FIntField: Integer;

FStrField: String; end;

Var

aObject: TMyClass;

Begin

aObject.FIntField := 0;

aObject.FStrField := 'Строка символов';

end;

Класс-потомок получает все поля всех своих предков и может дополнять их своими, но он не может переопределять их или удалять.

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

Методы

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

Type

TMyClass = class

FunctionMyFunc(aPar: Integer): Integer;

ProcedureMyProc;

end;

Доступ к методам класса, как и к его полям, возможен с помощью составных имен:

Var

aObject: TMyClass;

Begin

aObject.MyProc;

end;

Методы класса могут перекрываться в потомках. Например:

Type

TParentClass = class ProcedureDoWork;

end;

TChildClass = class(TParentClass) ProcedureDoWork;

end;

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

В Object Pascal гораздо чаще используется динамическое замещение методов на этапе прогона программы. Для реализации этого метод, замещаемый в родительском классе, должен объявляться как динамический (с директивой dynamic) или виртуальный (virtual). Встретив такое объявление, компилятор создаст две таблицы -DMT (Dynamic Method Table) и VMT (Virtual Method Table) и поместит в них адреса точек входа соответственно динамических и виртуальных методов. При каждом обращении к замещаемому методу компилятор вставляет код, позволяющий извлечь адрес точки входа в подпрограмму из той или иной таблицы. В классе-потомке замещающий метод объявляется с директивой override (перекрыть). Получив это указание, компилятор создаст код, который на этапе прогона программы поместит в родительскую таблицу точку входа метода класса-потомка, что позволит родителю выполнить нужное действие с помощью нового метода.

Пусть, например, родительский класс с помощью методов show и Hide соответственно показывает что-то на экране или прячет изображение. Для создания изображения он использует метод Draw с логическим параметром:

Type

TVisualObject = class(TWinControl)

ProcedureHide;

ProcedureShow;

ProcedureDraw(IsShow: Boolean); virtual;

end;

TVisualChildObject = class(TVisualObject)

ProcedureDraw(IsShow: Boolean); override;

end;

Реализация методов show и Hide очень проста:

ProcedureTVisualObject.Show;

Begin

Draw(True) ;

end;

ProcedureTVisualObject.Hide;

Begin

Draw(False) ;

end;

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

Разница между динамическими и виртуальными методами состоит в том, что таблица динамических методов DMT содержит адреса только -тех методов, которые объявлены как dynamic в данном классе, в то время как таблица VMT содержит адреса виртуальных методов не только данного класса, но и всех его родителей. Значительно большая по размеру таблица VMT обеспечивает более быстрый поиск, в то время как при обращении к динамическому методу программа сначала просматривает таблицу DMTу объекта, затем -у его родительского класса и так далее, пока не будет найдена нужная точка входа.

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

Type

TVisualObject = class(TWinControl)

ProcedureDraw(IsShow: Boolean); virtual; abstract;

end;

TVisualChildObject = class(TWinControl)

Procedure Draw(IsShow: Boolean); override; end;

Var

aVisualObject: TVisualObject;

aVisualChild: TVisualChildObject ;

Begin

aVisualObject.Show; {Ошибка/ Обращение к абстрактному методу}
aVisualChild.Show;
{Нормальное обращение. Метод Draw у класса TVisualChildObject перекрыт.)

end;

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

В состав любого класса входят два специальных метода -конструктор и деструктор. У класса TObject эти методы называются create и Destroy, так же они называются в подавляющем большинстве его потомков. Конструктор распределяет объект в динамической памяти и помещает адрес этой памяти в переменную self, которая автоматически объявляется в классе. Деструктор удаляет объект из кучи. Обращение к конструктору должно предварять любое обращение к полям и некоторым методам объекта. По своей форме конструкторы и деструкторы являются процедурами, но объявляются с помощью зарезервированных слов constructor и Destructor:

Type

TMyClass = classIntField: Integer; ConstructorCreate(Value: Integer);

DestructorDestroy;

end;

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

Var

MyObject: TMyClass;

Begin

MyObject.IntField := 0;

{ Ошибка! Объект не созданконструктором!}

MyObject := TMyClass.Create;

// Надо так: создаем объект

MyObject.IntField := 0;

// и обращаемся к его полю

MyObect.Free;

// Уничтожаем ненужный объект

end;

В базовом классе TObject определен метод Free, который сначала проверяет действительность адреса объекта и лишь затем вызывает деструктор Destroy. Обращение к деструктору объекта будет ошибочным, если объект не создан конструктором, поэтому для уничтожения ненужного объекта следует вызывать метод Free, как это сделано в предыдущем примере.

Большинство конструкторов реализуют некоторые действия, необходимые для правильной работы объекта. Поэтому в конструкторе класса-потомка следует сначала вызвать конструктор своего родителя, а уже затем осуществлять дополнительные действия. Вызов любого метода родительского класса достигается с помощью зарезервированного слова inherited (унаследованный):

ConstructorTMyClass.Create(Value: Integer);

// Возможная реализация конструктора

begin

InheritedCreate; // Вызываем унаследованный конструктор IntField := Value; // Реализуем дополнительные действия

end;

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

Type

TMyClass = class(TObject)

class FunctionGetClassName: String;

end;

Var

S: String;

Begin

S := TMyClass.GetClassName;

end;

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

Одноименные методы

При обнаружении одноименного метода компилятор Delphi предупреждает о том, что у класса уже есть аналогичный метод с дру гими параметрами. Для подавления сообщений объявление одноименного метода можно сопровождать зарезервированным словом reintrpduce (вновь ввести).

Примечание:Чтобы одноименные методы можно было отличить друг от друга,каждый из них должен иметь уникальный набор, параметров. В ходе шлпоянения программы при: обращении к одному, из одноименнх методов программа проверяет; тип и количество фактических параметров обращения и выбирает нужный метод

В следующем примере в классе TForm1 используются целых 4 одноименных метода close. Лишь один из них - унаследованный метод без параметра выполняет свои основные функции - закрывает окно. Три других отличаются набором параметров и выводят сообщение в заголовок окна.

Поместите на пустую форму четыре кнопки TButton и напишите такие обработчики их событий OnClick:

procedureTForm1.ButtonlClick(Sender: TObject);

Begin

Close('Строка символов')

end;

procedureTFormi.Button2Click(Sender: TObject);

Begin

Close(123)

end;

procedureTFormi.ButtonSClick(Sender: TObject);

Begin

Close (20,300) ;

end;

procedureTFormi.Button4Click(Sender: TObject);

Begin

Close end;

Теперь в раздел private класса Tform1 вставьте три таких объявления методов close:

Private

{ Private declarations }

procedureClose(S: String);

reintroduce;

overload;

procedureClose(I: Integer);

reintroduce;

overload;

procedureClose(I,J: Integer);

reintroduce;

overload;

И, наконец, в разделе implementation поместите описания объявленных методов:

procedureTForm1.Close(S: String) ;

Begin

Caption := S end;

procedureTFormI.Close(I: Integer);

Begin

Caption := IntToStr(I) end;

procedureTFormI.close(I,J: Integers);

Begin

Caption := IntToStr(i*j)

end;

Теперь после запуска программы три первые кнопки будут вызывать методы close класса Tform1 и менять заголовок окна, в то время как кнопка Button4 обратится к методу close родительского класса т Form и закроет окно.

Свойства

Свойства - это специальный механизм классов, регулирующий доступ к полям. Свойства объявляются с помощью зарезервированных СЛОВ property, read И write (слова read И write считаются зарезервированньши только в контексте объявления свойства). Обычно свойство связано с некоторым полем и указывает те методы класса, которые должны использоваться при записи в это поле или при чтении из него. Например:

Type

TaClass = class

IntField: Integer; Function GetField: Integer;

ProcedureSetField (Value: Integers);

PropertyIntegerValue: Integer readGetField

writeSetField;

end ;

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

Var

aClass: TaClass;

Value: Integer;

Begin

aClass := TaClass.Create; { Обязательное обращение к

конструктору перед обращением к полю или свойству!} aClass.IntegerValue := 0;

Value := aClass.IntegerValue;

aClass.Destroy; // Удаление ненужного объекта

end;

Более того, возможен и такой оператор присваивания:

aClass.IntField := NewValue;

Разница между этим оператором и оператором

aClass.IntegerValue := NewValue;

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

IbOutput.Caption := 'Строка';

Свойство Caption компонента Label вызывает метод setText, который не только запоминает строку символов во внутренней переменной, но и осуществляет прорисовку метки с новым текстом.

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

Type

TaClass = classIntFiled: Integer;

ProcedureSetFieid (Value: Integers;

PropertyIntegerValue:

Integer readIntFiled writeSetFieid;

end;

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

Объявление класса

Любой вновь создаваемый класс может содержать секции (разделы), определяемые зарезервированными словами published(опубликованные), private (закрытые), protected (защищенные), public(доступные) и automated(автоматизированные). Внутри каждой секции вначале определяются поля, а затем - методы и свойства.

Секции определяют области видимости элементов описания класса. Секция public не накладывает ограничений на область видимости перечисляемых в ней полей, методов и свойств - их можно вызывать в любом другом модуле программы. Секция published также не ограничивает область видимости, однако в ней перечисляются свойства, которые должны быть доступны не только на этапе исполнения, но и на этапе конструирования программы (т. е. в окне Инспектора объектов). Секция published используется только при разработке нестандартных компонентов. Заметим, что среда Delphi помещает описания компонентов, вставленных в форму, в специальную секцию без названия, которая располагается сразу за заголовком класса и продолжается до первой объявленной секции. Эта секция - published. Программисту не следует помещать в нее собственные элементы описания класса или удалять из нее элементы, вставленные средой. Секция private сужает область видимости до минимума: закрытые элементы описания доступны только внутри методов данного класса и подпрограммах, находящихся в том же модуле, где описан класс. Элемент, объявленный в секции private, становится недоступным даже ближайшим потомкам класса, если они размещаются в других модулях. Секция protected доступна только методам самого класса, а также любым его потомкам, независимо от того, находятся ли они в том же модуле или нет. Наконец, секция automated используется только для объявления свойств и методов, которые будут добавлены к так называемому интерфейсу OLE-объектов Автоматизации; область видимости членов этой секции не ограничена.

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

Следующий фрагмент кода поясняет области видимости.

UnitUnit1;

Interface

Uses Controls, Forms;

Type

TForm1 = class(TForm)

Button1: TButton; // Эта секция обслуживается Delphi

// Ее элементы доступны всем
// Эта секция доступна в модуле Uniti

private
FIntField: Integers
ProcedureSetValue(Value: Integers);
FunctionGetValue: Integer;
published
// Эта секция доступна в любом модуле

PropertyIntField: readGetValue writeSetValue;

protected// Эта секция доступна классам-потомкам

ProcedureProc1;

public// Эта секция доступна в любом модуле ProcedureProc2;

end;

Var

Formi: TForm1;

Implementation ProcedureTFormI.Proc1 ;

Buttoni.Color := clBtnFace;1

// Так можно

FIntField := 0;

// Так можно

IntField := 0;1

// Так можно Proc1;

// Так можно Proc2;1

// Так можно

end;

Begin

Form1.Button1.Color := clBtnFace; // Так можно

Form1.FIntField := 0; // Так можно

Form1.IntField := 0; // Так можно
Form1.Proc1; // Так нельзя!
Form1.Proc2; // Так можно

End.

UnitUnit2;

Interface

UsesControls, Unit1;

Type

TForm2 = class(TFormI) Button2: TButton;

Procedure Button2Click(Sender: TObject);

end;

Var

Form2: TForm2;

Implementation

ProcedureTForm2.Button2Click(Sender: TObject);

Begin

Buttoni.Color := clBtnFace; // Так можно

FIn'tField := 0; // Так нельзя!

IntField := 0; // Так можно

Proc1; // Так можно

Proc2; // Так можно

end;

Begin

Form1.Buttoni.Color := clBtnFace; // Так можно

Form1.FIntField := 0; // Так нельзя!

Form1.IntField := 0; // Так можно

Form1.Proc1; //Так нельзя!

Form1.Proc2; // Так можно

End.

При объявлении класса-потомка разрешается перемещать элементы класса из одной области видимости в другую. Для предыдущего примера допустимо такое объявление:

Type

TForm2 = class(Tform1)

Public

ProcedureProc1;

end;

После этого в модуле unit2 возможно такое обращение:

Form2.Proc1;

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

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








Дата добавления: 2016-03-04; просмотров: 678;


Поиск по сайту:

При помощи поиска вы сможете найти нужную вам информацию.

Поделитесь с друзьями:

Если вам перенёс пользу информационный материал, или помог в учебе – поделитесь этим сайтом с друзьями и знакомыми.
helpiks.org - Хелпикс.Орг - 2014-2024 год. Материал сайта представляется для ознакомительного и учебного использования. | Поддержка
Генерация страницы за: 0.09 сек.