Classes в Delphi


Содержание

Лекция 14. ООП (Классы. — глава из учебника [3])

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

Класс — это тип данных, определяемый пользователем. То, что в Delphi имеется множество предопределенных классов, не противоречит этому определению -ведь разработчики Delphi тоже пользователи Object Pascal.

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

В любом вашем Делфи-приложении вы можете увидеть строки:

Это объявление класса TForml вашей формы и объявление переменной Forml -объекта этого класса.

В общем случае синтаксис объявления класса следующий:

Имя класса может быть любым допустимым идентификатором. Но принято идентификаторы большинства классов начинать с символа «Т». Имя класса — родителя может не указываться. Тогда предполагается, что данный класс является непосредственным наследником TObject — наиболее общего из предопределенных классов. Таким образом, эквивалентны следующие объявления: В приведенном ранее объявлении класса формы TForml видно, что его родительским классом является класс TForm.

Класс наследует поля, методы, свойства, события от своих предков и может отменять какие-то из этих элементов класса или вводить новые. Доступ к объявляемым элементам класса определяется тем, в каком разделе они объявлены.

Раздел public (открытый) предназначен для объявлений, которые доступны для внешнего использования. Это открытый интерфейс класса. Раздел published (публикуемый) содержит открытые свойства, которые появляются в процессе проектирования на странице свойств Инспектора Объектов и которые, следовательно, пользователь может устанавливать в процессе проектирования. Раздел private (закрытый), содержит объявления полей, процедур и функций, используемых только внутри данного класса. Раздел protected (защищенный) содержит объявления, доступные только для потомков объявляемого класса. Как и в случае закрытых элементов, можно скрыть детали реализации защищенных элементов от конечного пользователя. Однако в отличие от закрытых, защищенные элементы остаются доступны для программистов, которые захотят производить от этого класса производные объекты, причем не требуется, чтобы производные объекты объявлялись в этом же модуле.

Объявления полей выглядят так же, как объявления переменных или объявления полей в записях:

В приведенном ранее объявлении класса формы вы можете видеть строку

Это объявление объекта (поля) Buttonl типа (класса) TButton.

Имеется одно очень существенное отличие объявления поля от обычного объявления переменной: в объявлении поля не разрешается его инициализация каким-то значением. Автоматически проводится стандартная инициализация: порядковым типам в качестве начального значения задается 0, указателям — nil, строки задаются пустыми. При необходимости задания других начальных значений используются конструкторы, описанные далее в разд. 3.5.3.

Объявления методов в простейшем случае также не отличаются от обычных объявлений процедур и функций.

3.5.2 Свойства

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

Свойство объявляется оператором вида:

Если в разделах read или write этого объявления записано имя поля, значит предполагается прямое чтение или запись данных (т.е. обмен данными непосредственно с полем).

Если в разделе read записано имя метода чтения, то чтение будет осуществляться только функцией с этим именем. Функция чтения — это функция без параметра, возвращающее значение того типа, который объявлен для свойства. Имя функции чтения принято начинать с префикса Get, после которого следует имя свойства.

Если в разделе write записано имя метода записи, то запись будет осуществляться только процедурой с этим именем. Процедура записи — это процедура с одним параметром того типа, который объявлен для свойства. Имя процедуры записи принято начинать с префикса Set, после которого следует имя свойства.

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

Директивы запоминания определяют, как надо сохранять значения свойств при сохранении пользователем файла формы .dfm. Чаще всего используется директива default — значение по умолчанию.
Она не задает начальные условия. Это дело конструктора. Директива просто говорит, что если пользователь в процессе проектирования не изменил значение свойства но умолчанию, то сохранять значение свойства не надо.

Приведем пример. Мы уже не раз использовали объекты, содержащие сведения о сотруднике некоторой организации. Но раньше мы реализовывали эти объекты в виде записей (см. разд. 3.3). А теперь рассмотрим более общий подход — реализацию класса подобных объектов.

Начните новый проект. Объявление класса, который мы хотим создать, можно поместить непосредственно в файл модуля. Но если вы хотите создать класс, который будете использовать в различных проектах, лучше оформить его в виде отдельного модуля unit, сохранить в каталоге своей библиотеки или библиотеки Delphi, и подключать в дальнейшем к различным проектам с помощью предложения uses. Если вы забыли, как создается отдельный модуль, не связанный с формой, и как он сохраняется в библиотеке, посмотрите все это в разд. 2.8.7.4. Мы выберем именно этот вариант. Так что выполните команду File | New | Unit (в некоторых более старых версиях Delphi — File | New и на странице New выберите пиктограмму Unit). Сохраните сразу этот ваш модуль в библиотеке под именем, например, MyClasses. А в модуль формы введите оператор, ссылающийся на этот модуль:

Теперь займемся созданием класса в модуле My > Вглядимся в приведенный код. Интерфейсный раздел модуля interface начинается с предложения uses. Заранее включать это предложение в модуль не требуется. Но по мере написания кода вы будете встречаться с сообщениями компилятора о неизвестных ему идентификаторах функций, типов и т.п. Столкнувшись с таким сообщением, надо посмотреть во встроенной справке Delphi или в справке [3], в каком модуле объявлена соответствующая функция или класс. И включить этот модуль в приложение uses.

Теперь обратимся к объявлению класса. Объявленный класс TPerson наследует непосредственно классу TObject, поскольку родительский класс не указан. В закрытом разделе класса private объявлен ряд полей. Поле FName предполагается использовать для фамилии, имени и отчества человека. Поля FDepl, FDep2, FDep3 будут использоваться под указание места работы или учебы. Поле FYear будет хранить год рождения, поле FSex — указание пола: символ «м» или «ж». Поле FAttr будет хранить какую-то характеристику: штатный — нештатный, отличник или нет и т.п. Поле FComment предназначено для каких-то текстовых комментариев. В частности, в нем можно хранить свойство Text многострочного окна редактирования Memo или RichEdit. Так что это может быть развернутая характеристика человека, правда, без форматирования.

В открытом разделе класса public объявлены свойства, соответствующие всем полям. Чтение всех свойств осуществляется непосредственно из полей. Запись во всех свойствах, кроме Sex, осуществляется тоже непосредственно в поля. А для поля Sex указана в объявлении свойства процедура записи SetSex, поскольку надо следить, чтобы по ошибке в это поле не записали символ, отличный от «м» и «ж». Соответственно в защищенном разделе класса protected содержится объявление этой процедуры. Как говорилось ранее, она должна принимать единственный параметр типа, совпадающего с типом свойства.

В раздел модуля implementation введена реализация процедуры записи SetSex. Ее заголовок повторяет объявление, но перед именем процедуры вводится ссылка на класс TPerson, к которому она относится. Не забывайте давать такие ссылки для методов класса. Иначе получите сообщение компилятора об ошибке; Unsatisfied forward or external declaration: TPerson.SetSex — нереализованная ранее объявленная или внешняя функция TPerson.SetSex.

Тело процедуры SetSex в особых комментариях не нуждается. В нем проверяется допустимость символа, переданного в процедуру через параметр Value. Если символ допустимый, то его значение заносится в поле FSex. Это поле, как и другие закрытые поля, открыто для методов данного класса. При ошибочном символе пользователю выдается сообщение об ошибке. Правда, лучше было бы в этом случае сгенерировать исключение, но пока мы не обсуждали, как это можно делать.

Вы создали класс в вашем модуле MyClasses. Давайте посмотрим, как можно использовать объекты нашего класса. Создайте в модуле формы Unit1 вашего приложения тест класса TPerson. Введите в модуль операторы:

uses Classl;
var Pers: TPerson;

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

Создание объекта вашего класса TPerson должно осуществляться вызовом его конструктора Create. Так что создайте обработчик события OnCreate вашей формы, и вставьте в него оператор:

Вот теперь объект создан, и переменная Pers указывает на него. Чтобы не забыть очистить память от этого объекта при завершении работы приложения, сразу создайте обработчик события OnDestroy формы, и вставьте в него оператор:

Почему ваш объект воспринимает методы Create и Free? Ведь вы их не объявляли в классе TPerson! Это работает механизм наследования. В классе TObject, являющемся родительским для TPerson, методы Create и Free имеются. А поскольку вы их не перегружали, то ваш класс наследует их.

Теперь перенесите на форму четыре окна Edit, окно Memo и две кнопки. Пусть первая кнопка с надписью Запись заносит в объект Pers данные из окон редактирования: ‘ фамилию с именем и отчеством, пол, подразделение, в котором работает или обучается человек, год рождения, характеристику из окна Memo. Обработчик щелчка на ней может иметь вид:

А вторая кнопка с надписью Чтение пусть осуществляет чтение информации из объекта в окна редактирования. Обработчик щелчка на ней может иметь вид:

Выполните ваше приложение. Занесите в окна редактирования какую-то подходящую информацию и щелкните на кнопке Запись. А потом сотрите тексты всех окон редактирования и щелкните на кнопке Чтение. Информация в окнах должна восстановиться. Проверьте также реакцию на неверный символ, задающий пол.

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

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

Класс TObject является предком всех других классов, используемых в DELPHI. Он включает в себя характеристики, свойственные всем используемым классам. Некоторые методы класса TObject могут использоваться без создания соответствующих объектов с учетом того, что реального объекта такого класса может и не быть. Эти методы позволяют получить общие характеристики класса — адрес таблицы, содержащей характеристики класса, имя класса, имя предка класса, характеристики методов и т. д. Примеры некоторых методов класса TObject:
• ClassName — функция класса (типа ShortString) формирует строку, содержащую имя класса, данное ему при создании;
• ClassParent — функция, определяющая класс непосредственного предка данного класса;
• ClassType — функция возвращает класс конкретного объекта;
• InstanceSize — функция (типа Longint) возвращает размер класса или объекта в байтах;
• FieldAddress (Name) — функция типа Pointer возвращает адрес поля объекта с именем Name типа ShortString.
Класс TPersistent (Постоянный) является потомком класса TObject и предком всех классов, объекты которых могут быть помещены в память и взяты из памяти. Основными потомками класса TPersistent являются классы TComponent (Компонента) — предок всех компонент проекта; TStrings (Строки) — предок всех списков строк; TCollection (Коллекция) — коллекция (список) элементов; TGraphicObject (Графический объект), TCanvas (Канва — основа для рисования), TGraphic (Графический элемент), TPicture (Изображение) — классы, образующие так называемый графический инструментарий DELPHI .
Класс TControl является родоначальником всех элементов управления, с помощью которых выводится информация на экран и с помощью которых можно вводить информацию в программу, используя клавиатуру и мышь. Его потомок класс TWinControl служит для создания окон Windows. Класс TGraphicControl отличается от класса TWinControl отсутствием у объектов его семейства оконной функции, в связи с чем такие элементы либо служат для вывода на экран информации, либо являются чисто декоративными.
Методы базового класса Tobject , как отмечалось выше, позволяют легко получать информацию о характеристиках созданного класса и его потомках.
В Object Pascal можно задавать указатель на класс, называемый метаклассом. Для этого в объявлении записываются ключевые слова class of, после чего указывается имя класса, ссылка на который формируется:
type
=class of ;
Примером может быть следующее описание:

var ControlClass: TControlClass;

Ссылки по теме

Популярные статьи
Информационная безопасность Microsoft Офисное ПО Антивирусное ПО и защита от спама Eset Software


Бестселлеры
Курсы обучения «Atlassian JIRA — система управления проектами и задачами на предприятии»
Microsoft Office 365 для Дома 32-bit/x64. 5 ПК/Mac + 5 Планшетов + 5 Телефонов. Подписка на 1 год. Электронный ключ
Microsoft Windows 10 Профессиональная 32-bit/64-bit. Все языки. Электронный ключ
Microsoft Office для Дома и Учебы 2020. Все языки. Электронный ключ
Курс «Oracle. Программирование на SQL и PL/SQL»
Курс «Основы TOGAF® 9»
Microsoft Windows Professional 10 Sngl OLP 1 License No Level Legalization GetGenuine wCOA (FQC-09481)
Microsoft Office 365 Персональный 32-bit/x64. 1 ПК/MAC + 1 Планшет + 1 Телефон. Все языки. Подписка на 1 год. Электронный ключ
Windows Server 2020 Standard
Курс «Нотация BPMN 2.0. Ее использование для моделирования бизнес-процессов и их регламентации»
Антивирус ESET NOD32 Antivirus Business Edition
Corel CorelDRAW Home & Student Suite X8

О нас
Интернет-магазин ITShop.ru предлагает широкий спектр услуг информационных технологий и ПО.

На протяжении многих лет интернет-магазин предлагает товары и услуги, ориентированные на бизнес-пользователей и специалистов по информационным технологиям.

Хорошие отзывы постоянных клиентов и высокий уровень специалистов позволяет получить наивысший результат при совместной работе.

Classes в Delphi

На этом шаге мы напомним общие понятия о классах и рассмотрим описание классов в Delphi .


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

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

Тип класс — это структура данных, состоящая из полей, методов, свойств . Поля содержат данные определенного типа. Методы — это функции и процедуры, описанные внутри класса и предназначенные для операций над его полями. Свойства — это специальный механизм классов, регулирующий доступ к полям. Свойство описывает один или два метода, которые осуществляют некоторые действия над данными того же типа, что и свойство. Например, обычная кнопка в окне приложения обладает такими свойствами, как цвет, размеры, положение. Для экземпляра класса «кнопка» значения этих свойств представлены специальными переменными, определяемыми ключевым словом Property . Цвет может задаваться свойством Color , размеры — свойствами Width и Height и т.д.

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

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

В данном примере доступ к значению свойства MyColor осуществляется через вызовы методов GetColor и SetColor . К методам в явном виде не обращаются, достаточно записать:

и компилятор оттранслирует эти операторы в вызовы соответствующих методов: записи ( SetColor ) и чтения ( GetColor ).

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

FOnMyEvent1, FOnMyEvent2 — поля, содержащие адреса некоторых методов. Присвоить такому свойству значение — значит, указать объекту адрес метода, который будет вызываться в момент наступления события . Такие методы называют обработчиками событий. Таким образом, обработчик события — фрагмент программы, который выполняется в ответ на определенное изменение в программе или в Windows .

Каждый новый класс в Delphi должен быть объявлен глобально. Для этого используется зарезервированное слово Class . Объявление определяет функциональные возможности класса. Объявление классов в модуле производится в разделе объявления типов. Пример объявления класса и объекта в Delphi :

В объявлении типа определен новый класс — TForm1 , наследуемый от класса TForm , содержащегося в библиотеке визуальных компонентов. На это указывает зарезервированное слово Class . Данный тип содержит указатели на компоненты, которые были помещены на форму:

  • один компонент Label1 — объект типа TLabel (иначе говоря, экземпляр класса TLabel ),
  • один компонент Edit1 — объект типа TEdit (экземпляр класса TEdit ) и
  • два экземпляра класса TBitBtn .

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

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

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

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

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

Свойства, расположенные в разделе опубликованных объявлений , отображаются в Инспекторе объектов (Object Inspector) .

На следующем шаге мы поговорим об иерархии классов .

Classes в Delphi

Класс — это тип данных, определяемый пользователем. То, что в Delphi имеется множество предопределенных классов, не противоречит этому определению -ведь разработчики Delphi тоже пользователи Object Pascal.

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

В любом вашем Делфи-приложении вы можете увидеть строки:

Это объявление класса TForml вашей формы и объявление переменной Forml -объекта этого класса.

В общем случае синтаксис объявления класса следующий:

Имя класса может быть любым допустимым идентификатором. Но принято идентификаторы большинства классов начинать с символа «Т». Имя класса — родителя может не указываться. Тогда предполагается, что данный класс является непосредственным наследником TObject — наиболее общего из предопределенных классов. Таким образом, эквивалентны следующие объявления:

В приведенном ранее объявлении класса формы TForml видно, что его родительским классом является класс TForm.

Класс наследует поля, методы, свойства, события от своих предков и может отменять какие-то из этих элементов класса или вводить новые. Доступ к объявляемым элементам класса определяется тем, в каком разделе они объявлены.

Раздел public (открытый) предназначен для объявлений, которые доступны для внешнего использования. Это открытый интерфейс класса. Раздел published (публикуемый) содержит открытые свойства, которые появляются в процессе проектирования на странице свойств Инспектора Объектов и которые, следовательно, пользователь может устанавливать в процессе проектирования. Раздел private (закрытый), содержит объявления полей, процедур и функций, используемых только внутри данного класса. Раздел protected (защищенный) содержит объявления, доступные только для потомков объявляемого класса. Как и в случае закрытых элементов, можно скрыть детали реализации защищенных элементов от конечного пользователя. Однако в отличие от закрытых, защищенные элементы остаются доступны для программистов, которые захотят производить от этого класса производные объекты, причем не требуется, чтобы производные объекты объявлялись в этом же модуле.

Объявления полей выглядят так же, как объявления переменных или объявления полей в записях:

В приведенном ранее объявлении класса формы вы можете видеть строку

Это объявление объекта (поля) Buttonl типа (класса) TButton.

Имеется одно очень существенное отличие объявления поля от обычного объявления переменной: в объявлении поля не разрешается его инициализация каким-то значением. Автоматически проводится стандартная инициализация: порядковым типам в качестве начального значения задается 0, указателям — nil, строки задаются пустыми. При необходимости задания других начальных значений используются конструкторы, описанные далее в 4 части.

Объявления методов в простейшем случае также не отличаются от обычных объявлений процедур и функций.

В следующем уроке мы узнаем о свойствах классов.

Разработка компонентов в среде Delphi

Все компоненты Delphi являются частью иерархии, которая называется Visual Component Library (VCL). Общим предком всех компонентов является класс TComponent (рис. 9.1.1), в котором собран минимальный набор общих для всех компонентов Delphi свойств.

Свойство ComponentState содержит набор значений, указывающих на текущее состояние компонента. Приведем некоторые значения свойства:

csDesigning компонент находится в режиме проектирования
sDestroyingKOMnoHeHT сейчас будет разрушен;
csLoading компонент загружается из файла формы;
csReading компонент считывает значения из файла формы;
csWriting компонент записывает значения своих свойств в поток;
csUpdating компонент вносит изменения, чтобы отразить изменения в родительской форме.

Класс TComponent вводит концепцию принадлежности. Каждый компонент имеет свойство Owner (владелец), ссылающееся на другой компонент как на своего владельца. В свою очередь, компоненту могут принадлежать другие компоненты, ссылки на которые хранятся в свойстве Components. Конструктор ком­понента принимает один параметр, который используется для задания владельца компонента. Если передаваемый владелец су­ществует, то новый компонент добавляется к списку Components владельца. Свойство Components обеспечивает автоматическое разрушение компонентов, принадлежащих владельцу. Свойст­во ComponentCount показывает количество принадлежащих компонентов, a Componentlndex — номер компонента в массиве Components.

В классе TComponent определено большое количество мето­дов. Наибольший интерес представляет метод Notification. Он вызывается всегда, когда компонент вставляется или удаляется из списка Components владельца. Владелец посылает уведомле­ние каждому члену списка Components. Этот метод переопределя­ется в порождаемых классах для того, чтобы обеспечить действи­тельность ссылок компонента на другие компоненты. Например, при удалении компонента Tablel с формы свойство DataSet компонента DataSourcel, равное Tablel, устанавливается в Nil.

Процесс разработки компонента включает пять этапов:

создание модуля компонента;

добавление в новый компонент свойств, методов и событий;

регистрацию компонента в среде Delphi;

На рис. 9.1.1 изображены базовые классы, формирующие структуру VCL. В самом верху расположен TObject, который является предком для всех классов в Object Pascal. От него про­исходит TPersistent, обеспечивающий методы, необходимые для создания потоковых объектов. Потоковый объект — объект, ко­торый может запоминаться в потоке. Поток представляет собой объект, способный хранить двоичные данные (файлы). Поскольку Delphi реализует файлы форм, используя потоки, то TComponent порождается от TPersistent, предоставляя всем компонентам способность сохраняться в файле формы.

Класс TComponent представляет собой вершину иерархии компонентов и является первым из четырех базовых классов, используемых для создания новых компонентов. Прямые по­томки TComponent — невизуальные компоненты.

Вершину иерархии визуальных компонентов представляет класс TControl.

Класс TControl вводит понятие родительских элементов управ­ления (parent control). Свойство Parent является окном, кото­рое содержит элемент управления. Например, если компонент Panel 1 содержит Button 1, то свойство Parent компонента Button 1 равно Panel 1.

Свойство ControlStyle определяет различные стили, приме­нимые только к визуальным компонентам, например:


csAcceptControls элемент управления становится родителем любых элементов управления, помещенных на него во время проектирования. Применим только к оконным элементам управления;
csCaptureMouse элемент управления перехватываетсобытия мыши;
сsFrames элемент управления имеет рамку;
csSetCaption свойства Caption и Text элемента управления (если не заданы явно) устанавливаются так, чтобы совпадать со свойством Name;
csOpaque элемент управления скрывает все элементы позади себя.

В классе TControl определено большинство свойств, использу­емых визуальными компонентами: свойства позиционирования (Align, Left, Top, Height, Width), свойства клиентской области (ClientHeight, ClientWidth), свойства внешнего вида (Color, Enabled, Font, ShowHint, Visible), строковые свойства (Caption, Name, Text, Hint), свойства мыши (Cursor, DragCursor, DragKind, DragMode).

Кроме того, класс TControl реализует методы диспетчеризации событий.

Все визуальные компоненты подразделяют на графические элементы управления и оконные элементы управления. Каж­дый тип представляет свою иерархию классов, происходящую соответственно от TGraphicControl и TWinControl. Главная раз­ница между этими типами компонент состоит в том, что графи­ческие компоненты не поддерживают идентификатор окна, и, соответственно, не могут принять фокус ввода.

Оконные компоненты далее разбиваются на две категории. Прямые потомки TWinControl являются оболочками вокруг су­ществующих элементов управления, реализованных в Windows (например, TEdit, TButton, и др.) и, следовательно, знают, как себя рисовать.

Для компонентов, которые требуют идентификатора окна, но не инкапсулируют базовых элементов Windows, которые бы обеспечивали возможность перерисовывать себя, имеется класс TCustomControl.

9.1.2. Класс TGraphicControl

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

По умолчанию объекты TGraphicControl не имеют собствен­ного визуального отображения, но для наследников обеспечи­ваются виртуальный метод Paint (вызывается всегда, когда элемент управления должен быть нарисован) и свойство Canvas (используется как «поверхность» для рисования).

Класс TWinControl используется как базовый для создания компонентов, инкапсулирующих соответствующие оконные эле­менты управления Windows, которые сами себя рисуют.

Класс TWinControl обеспечивает свойство Handle, являюще­еся ссылкой на идентификатор окна базового элемента управ­ления. Кроме этого свойства класс реализует свойства, методы и события, поддерживающие клавиатурные события и измене­ния фокуса:

свойства фокуса TabStop, TabOrder;
свойства внешнего вида Ctl3D, Showing;
методы фокуса CanFocus, Focused;
методы выравнивания AlignControl, EnableAlign, Re Align;
оконные методы CreateWnd, CreateParam, RecreateWnd, CreateWindowHandle, DestroyWnd;
события фокуса OnEnter, OnExit;
события клавиатуры OnKeyDown, OnKeyPress, OnKeyUp.

Создание любого потомка этого класса начинается с вызова ме­тода CreateWnd, который вначале вызывает CreateParams для инициализации записи параметров создания окна, а затем вызыва­ет CreateWindowHandle для создания реального идентификатора окна, использующего запись параметров. Затем CreateWnd настра­ивает размеры окна и устанавливает шрифт элемента управления.

9.1.4. Класс TCustomControl

Класс TCustomControl представляет собой комбинацию клас­сов TWinControl и TGraphicControl. Являясь прямым потомком класса TWinControl, TCustomControl наследует способность управления идентификатором окна и всеми сопутствующими возможностями. Кроме этого, как и класс TGraphicControl, класс TCustomControl обеспечивает потомков виртуальным ме­тодом Paint, ассоциированным со свойством Canvas.

Таким образом, в зависимости от того, какой компонент бу­дет исходным (базовым) для создания нового класса, можно выделить 4 случая:

создание Windows-элемента управления (TWinControl);

создание графического элемента управления (TGraphic-Control);

создание нового элемента управления (TCustomControl); О создание невизуального компонента (TComponent).

9.2. Создание модуля компонента и тестового приложения

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

Выполните команду File/ New. / Component или Component/ New Component.

В диалоговом окне New Component (рис. 9.2.1.) установите основные параметры создания компонента: Ancestor type (имя класса-предка), Class Name (имя класса компонента), Palette Page (вкладка палитры, на которой должен отображаться ком­понент) и Unit file name (имя модуля компонента).

После щелчка на кнопке ОК будет сгенерирован каркас но­вого класса.

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

Упражнение 9.2.1. Разработайте новый компонент, который объединяет компоненты TEdit и TLabel. Компонент Label рас­полагается выше поля редактирования (TEdit). При перемеще­нии поля редактирования TLabel следует за ним. При удалении поля редактирования TLabel также удаляется.

В качестве предка класса нового компонента используем TEdit.

Выполните команду Component/ New component. Установите следующие значения параметров окна: Ancestor type TEdit

Class Name TLabelEdit

Palette Page Test

Unit file name . \LabelEdit\LabelEdit.pas

Щелкните на кнопке ОК, автоматически будет сгенерирован следующий код:

Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type

В модуле описан каркас нового класса и написана процедура регистрации компонента (Register), которая помещает его на страницу Test. Сохраните файл модуля компонента.

Разработка тестового приложения

Создайте новый проект. Сохраните его файлы в папке . \LabelEdit: файл модуля — под именем Main.pas, файл про­екта — Test Application, dpr.

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

В общедоступный раздел класса TForml добавьте поле

В обработчике события OnCreate формы динамически со­здайте новый компонент:

procedure TForml.FormCreate(Sender: TObject);

Сохраните файлы проекта.

Эксперимент. Убедитесь, что при запуске в левом верхнем углу формы появляется окно редактирования. ♦

9.3. Добавление свойств, методов и событий

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

Добавление свойства происходит в три этапа.

1. Создание внутреннего поля класса для хранения значения свойства.

2. Описание и разработка методов доступа к значению свойства.

3. Описание свойства.

В классе TControl свойства Caption/Text, Parent и Hint опре­деляются так:

TControl = class (TComponent)

function IsCaptionStored: Boolean;


function IsHintStored: Boolean;

procedure SetText(const Value: TCaption);

property Caption: TCaption read GetText write SetText stored IsCaptionStored;

property Text: TCaption read GetText write SetText;

property Parent: TWinControl read FParent write SetParent;

property Hint: string read FHint write FHint stored IsHintStored;

Объявление свойства имеет следующий синтаксис: property : тип определители;

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

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

Для указания метода, который будет использоваться для осуществления выборки значения свойства, используется ди­ректива read. Метод должен быть функцией, чей возвращае­мый тип является тем же самым, что и тип свойства.

Однако вместо метода доступа для чтения можно указать внутреннее поле хранения данных, как, например, при описа­нии свойств Hint и Parent. Подобная форма записи приводит к тому, что значение свойства извлекается прямо из внутреннего поля данных.

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

При обращении к значению свойства происходит перена­правление на соответствующий метод. Например, оператор s : =Editl. Text; автоматически будет преобразован в оператор s : =Editl. GetText; а оператор Editl. Text: =’ Test’ — в опе­ратор Editl.Text(‘Test’).

Описание свойства должно содержать определитель read или write или сразу оба. Если описание свойства включает в себя только определитель read, то оно является свойством только для чтения. В свою очередь, свойство, чье описание включает в себя только определитель write, является свойством только для записи. При присвоении свойству, определенному с директивой только для чтения, какого-либо значения или при использова­нии в выражении свойства с директивой только для записи все­гда возникает ошибка.

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

Когда программист использует Инспектор объектов для измене­ния свойств формы или свойств компонентов, то результирующие изменения заносятся в файл формы. Файлы форм представляют собой файлы ресурсов Windows, и когда приложение запускается, то описание формы подгружается из этого файла. Для определения того, что должно сохраняться в файле формы, служат специфика­торы памяти — необязательные директивы stored, default и node-fault. Эти директивы влияют на информацию о типе во время вы­полнения, генерируемую для свойств published.

Директива stored управляет тем, будет или нет свойство дейст­вительно запоминаться в файле формы. За директивой stored дол­жны следовать либо константы True или False, либо имя поля, имеющего тип Boolean, либо имя метода, у которого нет парамет­ров, и возвращающего значение типа Boolean. Например,

property Hint: string read FHint write FHint stored IsHintStored;

Если свойство не содержит директиву stored, то оно рассмат­ривается как содержащее ее с параметром True.

Директивы default и nodefault управляют значениями свой­ства по умолчанию. За директивой default должна следовать константа того же типа, что и свойство, например:

property Tag: Longint read FTag write FTag default 0 ;

Чтобы перекрыть наследуемое значение default без указания нового значения, используется директива nodefault. Директи­вы default и nodefault работают только с порядковыми типами и множествами, нижняя и верхняя границы которых лежат в промежутке от 0 до 31. Если такое свойство описано без дирек­тив default и nodefault, то оно рассматривается как с директи­вой nodefault. Для вещественных типов, указателей и строк значение после директивы default может быть только О, NIL и

(пустая строка) соответственно.

Когда Delphi сохраняет компонент, то просматриваются спе­цификаторы памяти published свойств компонента. Если значе­ние текущего свойства отличается от default значения (или ди­ректива default отсутствует) и параметр stored равен True, то значение свойства сохраняется, иначе свойство не сохраняется.

Спецификаторы памяти не поддерживаются свойствами-мас­сивами, а директива default при описании свойства-массива имеет другое назначение.

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

Рассмотрим создание простого свойства Color, описанного в классе TContol (модуль controls.pas):

TControl = class (TComponent)

function IsColorStored: Boolean;

procedure SetColor(Value: TColor);

property Color: TColor read FColor write SetColor stored IsColorStored default clWindow;

function TControl.IsColorStored: Boolean;

Result := not ParentColor;

procedure TControl.SetColor (Value: TColor);

if FColor <> Value then

Perform(CM_COLORCHANGED, 0, 0) ;

9.3.2. Свойства перечислимого типа

Определенные пользователем перечислимые и логические свойства можно редактировать в окне инспектора объектов, вы­бирая подходящее значение свойства в раскрывающемся списке. Рассмотрим создание свойства перечислимого типа на при­мере компонента Shape (модуль extctrls.pas).

TShapeType = (stRectangle, stSquare, stRoundRect, stRoundSquare, stEllipse, stCircle);

procedure SetShape(Value: TShapeType);

property Shape: TShapeType read FShape write SetShape

if FShape <> Value then

9.3.3. Свойства типа множества

Свойство типа множества при редактировании в окне Инспек­тора объектов выглядит так же, как множество, определенное синтаксисом языка Pascal. Простейший способ его отредактиро­вать — развернуть свойство в Инспекторе объектов, в результате каждый его элемент станет отдельным логическим значением.

При создании свойства типа множества нужно создать соот­ветствующий тип, описать методы доступа, после чего описать само свойство. В модуле Controls.pas свойсво Align описано сле­дующим образом:

TAlign = (alNone, alTop, alBottom, alLeft, alRight, alClient);

TAlignSet = set of TAlign; TControl = class(TComponent)

procedure SetAlign(Value: TAlign);

property Align: TAlign read FAlign write SetAlign default alNone;

procedure TControl.SetAlign(Value: TAlign);


var OldAlign: TAlign;

if FAlign <> Value then

if not (csLoading in ComponentState) and

(not (csDesigning in ComponentState) or (Parent <> NIL))

if ((OldAlign in [alTop, alBottom])=(Value in [alRight, alLeft])) and not (OldAlign in [alNone, alClient]) and not (Value in [alNone, alClient]) then SetBounds(Left, Top, Height, Width)

в соответствии со значением свойства Align >

Свойства могут являться объектами или другими компонен­тами. Например, у компонента Shape есть свойства-объекты Brush и Реп. Когда свойство является объектом, то оно может быть развернуто в окне инспектора так, чтобы его собственные свойства также могли быть модифицированы. Свойства-объек­ты должны быть потомками класса TPersistent, чтобы их свой­ства, объявленные в разделе published, могли быть записаны в поток данных и отображены в инспекторе объектов.

Для определения объектного свойства компонента необходимо сначала определить объект, который будет использоваться в каче­стве типа свойства. В модуле graphics.pas описан класс TBrush:

procedure GetData(var BrushData: TBrushData);

procedure SetData(const BrushData: TBrushData);

function GetBitmap: TBitmap;

procedure SetBitmap(Value: TBitmap);

function GetColor: TColor;

procedure SetColor(Value: TColor);

function GetHandle: HBrush.;

procedure SetHandle(Value: HBrush);

function GetStyle: TBrushStyle;

procedure SetStyle(Value: TBrushStyle);

constructor Create; destructor Destroy; override;

procedure Assign(Source: TPersistent); override;

property Bitmap: TBitmap read GetBitmap write SetBitmap;

property Handle: HBrush read GetHandle write SetHandle;

property Color: TColor read GetColor write SetColor

property Style: TBrushStyle read GetStyle write SetStyle

Метод Assign предназначен для копирования значения свойств экземпляра TBrush:

Блог GunSmoker-а

. when altering one’s mind becomes as easy as programming a computer, what does it mean to be human.

6 февраля 2013 г.

«Дружественность» в Delphi

Содержание

Инкапсуляция

Инкапсуляция — один из четырёх важнейших механизмов объектно-ориентированного программирования, заключающийся в объединении данных с методами и скрытии реализации (т.н. «чёрный ящик» — доступность главного, сокрытие второстепенного). Инкапсуляцию применяют для обеспечения согласованности внутреннего состояния объекта. Применение инкапсуляции позволяет предельно локализовать изменения (при необходимости таких изменений), прогнозировать изменения (какие изменения в коде нужно сделать для заданного изменения функциональности) и прогнозировать последствия изменений.

В ООП языках программирования инкапсуляция реализуется через механизмы ограничения доступа к членам класса путём введения ключевых слов типа private или protected . Общая идея состоит в том, что каждый класс выставляет наружу публичный ( public ) интерфейс, который и позволяет управлять объектом. Все открытые методы интерфейса объекта гарантировано переводят объект из одного согласованного и корректного состояния в другое. При этом внутренние «кишки» объекта скрыты от внешнего доступа, так что никакой сторонний код не может перевести объект из согласованного состояния в недопустимое. Однако применение директив вида protected позволяет наследникам класса получать доступ к его внутренним членам.

Необходимость обхода инкапсуляции

Но объекты не существуют автономно, сами по себе. Поэтому на практике часто возникают ситуации, когда необходимо обеспечить более тесное взаимодействие объектов, не связанных иерархией. Это подразумевает, что один объект должен уметь обращаться к другому объекту в обход его публичного интерфейса. Простой пример: объект-коллекция и объект-элемент коллекции. Эти два класса, скорее всего, не будут наследоваться друг от друга, а будут двумя разными ветвями дерева наследования. Тем не менее, при добавлении элемента в коллекцию нужно установить владельца (для элемента), его индекс и другие данные для учёта. Само собой, подобный сервис не должен выставляться наружу, поскольку тогда сторонний код мог бы его менять и, таким образом, «испортить» данные объекта. Так что публичный интерфейс будет содержать методы типа «добавить элемент в коллекцию», но не «теперь этот объект — в той коллекции, а его индекс там — минус сто».

Например: Здесь TCollection — коллекция, а TCollectionItem — элемент коллекции. Оба класса наследуются от общего предка TPersistent , но не друг от друга. И TCollection и TCollectionItem предоставляют открытый интерфейс для управления: метод добавления ( Add ) и получения информации (свойство Collection ), но не позволяют менять их на произвольные значения стороннему коду (закрытые поля FCollection и FItems ).

Разумеется, при добавлении элемента в коллекцию, необходимо внести элемент в список элементов ( FItems ) и назначить ему владельца ( FCollection ), поэтому этим двум классам необходимо иметь доступ к закрытым элементам друг друга в обход общего механизма разграничения доступа.

Понятие класса, дружественного другому классу

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

В Delphi нет специальных языковых конструкций для выделения дружественных классов. Вместо этого здесь используется иной механизм. А именно: любые классы в одном модуле (unit) автоматически считаются дружественными друг к другу и могут обращаться к закрытым элементам друг друга. Иными словами, расположение классов в одном модуле является необходимым и достаточным условием дружественности классов друг другу.

Поэтому в примере выше TCollection и TCollectionItem являются дружественными, поскольку расположены в одном модуле, так что допускается код типа такого: или такого:

Проблемы дружественности в Delphi

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

Сужение дружественности: strict

Понимая, что не всякий класс в модуле должен быть дружественным к другим классам этого же модуля, разработчики Delphi ввели новое ключевое слово strict , которое в комбинации с private и protected позволяют ограничивать доступ к членам класса для других классов в этом же модуле. Так, к примеру, к элементам класса в strict private не может получить доступа ни один код вне этого же класса, даже если этот код находится в том же модуле. Аналогично, доступ к элементам strict protected будут иметь только наследники этого класса, но не другие классы, даже если они будут находится в том же модуле.

Расширение дружественности: хак

Со второй проблемой (дружественные классы в разных модулях) несколько сложнее. В настоящее время Delphi не предоставляет никаких специальных языковых конструкций для разрешения этого конфликта. Вы не можете объявить классы дружественными друг другу, если они находятся в разных модулях. Поэтому программисту придётся использовать обходные пути.

Первый путь — самый простой и прямолинейный. Он заключается в использовании хака. Хотя обычно хак — это плохо, но данный конкретный хак безопасен. Заключается он в объявлении пустого класса-наследника в том же модуле, где нужно сделать дружественный класс. Например, пусть у нас есть класс: И в другом модуле есть класс, который должен обращаться к полю FItem : Чтобы разрешить этот конфликт (подразумевая, что мы не можем поместить TSomeClass и TAnotherClass в один модуль), мы можем сделать поле FItem как protected А для TAnotherClass использовать такую конструкцию: Объявляя класс-наследник ( TDummySomeClass ), мы делаем доступными ему все protected члены (и не важно, в каком модуле они расположены). А то, что этот класс-заглушка объявлен именно в модуле второго класса ( TAnotherClass ), сделает эти два класса дружественными, что и даст доступ к закрытому полю FItem . Обратите внимание, что при этом исходный класс ( TSomeClass ) не становится дружественным к TAnotherClass — вот почему нам необходимо выполнять явное преобразование типов.

Насколько безопасен такой хак? Ведь мы приводим объект одного класса ( TSomeClass ) к другому ( TDummySomeClass ), хотя он им не является. Ответ: достаточно безопасен. TDummySomeClass не содержит новых членов, мы не обращаемся к информации класса, мы используем только унаследованные элементы класса. Поэтому данное преобразование на уровне машинного кода — тождественно (т.е. вообще отсутствует). И никакие изменения в TSomeClass не приведут к поломке этого кода.

Разумеется, если у вас нет контроля над TSomeClass , то вы не сможете изменить область видимости FItem . В этом случае вы, конечно, можете использовать уже грязный хак (который является довольно опасным, т.к. произвольные изменения исходного класса могут его поломать). Но это не лучший вариант из-за опасной природы хака.


Расширение дружественности: интерфейсы

Итак, возвращаясь к нашим баранам, вариант второй разрешения конфликта — использование интерфейсов. Интерфейс (в смысле конструкции языка interface ) — это набор методов (читай: действий), которые можно произвести с объектом. Иными словами, это как бы «копия» public секции объекта. Вкусность тут в том, что их может быть много у одного объекта. Любой внешний код может запросить у объекта любой его сервис, если он знает его «имя» (в терминах интерфейсов: имя = GUID).

Таким образом, класс TSomeClass может реализовывать один или несколько интерфейсов, предназначенных для его дружественных классов, расположенных в других модулях. Чтобы подчеркнуть внутреннюю направленность интерфейсов, их можно вынести в отдельный недокументированный модуль. Тогда TAnotherClass может запросить у объекта класса TSomeClass интерфейс для внутренних манипуляций:
Как видите, вариант с интерфейсами — это гарантировано чистый путь, но неприятен он именно необходимостью смешивать объекты и интерфейсы (т.е. сущности с разным принципом управления временем жизни) в рамках одного класса. Как правило, чистота этого подхода не перевешивает его сложности по сравнению с чистым хаком, описанным выше.

Множественное наследование и интерфейсы

Но здесь, однако, кроется интересное для меня наблюдение. Если вы заметили, то объекты хорошо позволяют «реализовать что-то». У них есть наследование (и интерфейса и реализации), скрытие и дружественность, и т.д. Всё это позволяет легко реализовывать функциональность. Но не всегда это идеально удобно с точки зрения потребления/использования. Здесь уже ограничители (наследование, скрытие, . ) начинают скорее мешаться, чем приносить пользу. В самом деле, если я хочу добавить элемент в коллекцию, зачем мне всенепременно наследовать его от нужного класса? А если мне нужно добавить в коллекцию класс вне её дерева наследования? Ведь, по сути, здесь было бы достаточным, чтобы элемент поддерживал определённую функциональность (читай: «набор методов управления»). Для этого вовсе не обязательно строго наследовать от предопределённого класса, поскольку от элемента требуется лишь удовлетворять набору условий.

Насколько я понимаю, для решения этой проблемы (в том числе) в других языках программирования вводится поддержка множественного наследования: один класс может удовлетворять сразу нескольким функциональностям. К примеру, студент может быть не только человеком, но и рабочим, и музыкантом, и членом семьи (т.е. наследоваться от них в терминах множественного наследования). При этом, если наследуемые классы, в свою очередь, также наследуются друг от друга в различных комбинациях, то могут возникать конфликты (например, если «музыкант» и «член семьи» наследуют «прозвище» от «человек» с замещением реализации, то какую из реализаций должен брать «студент»?). Само собой, языки с поддержкой множественного наследования также содержат механизмы разрешения таких конфликтов. В целом же, множественное наследование часто критикуется. Проблемы, его чрезмерная сложность приводят критиков к выводу, что от него больше вреда, чем пользы. При этом также звучит мнение, что множественное наследование (как расширение «простого» одиночного наследования) — ошибочная концепция, порождённая неверным анализом и проектированием.

В Delphi проблемы множественного наследования решаются интерфейсами. Интерфейс — это полностью абстрактный класс, все методы которого виртуальны и абстрактны. Иными словами, говоря грубо, интерфейс — это запись ( record ) с указателями на функции. Каждый метод интерфейса должен быть реализован в классе. Причём реализацию вы либо пишете сами, либо наследуете, либо делегируете (для случая агрегации). Иными словами, любой интерфейс реализуется классом без конфликтов. Плюс, в каждый момент времени вы работаете с одним конкретным интерфейсом, а не с «объединённым набором интерфейсов», поэтому проблемы выбора нужной реализации тут просто нет — будет использоваться метод используемого интерфейса. Не та реализация? Берём другой (нужный) интерфейс с нужной реализацией.

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

В целом же, я считаю, что использование интерфейсов — это наиболее правильный вариант реализации множественного наследования, поскольку явное указание реализации исключает конфликты. Введение понятия интерфейсов является компромиссом, позволяющим получить преимущества множественного наследования, не реализуя его в полном объёме и, таким образом, не сталкиваясь со специфичными для него сложностями. Именно такой подход принят во многих современных языках — не только в Delphi, но и, к примеру, C# или Java.

Вы всё ещё используете объекты? Тогда мы идём к вам

Я уже кратко выразил основную мысль выше: объекты удобны для реализации функциональности (наследование/полиморфизм), но неудобны для использования. Добавлю только, что, помимо уже упомянутых ограничений, объекты неудобны ручным слежением за временем жизни. Интерфейсы же относятся к типам с автоматическим управлением временем жизни, что существенно упрощает разработку кода. Кроме того, интерфейсы — языко-независимы (т.е. ими можно обмениваться между кодом, написанных на разных языках), в отличие от классов, реализация которых своя в каждом языке программирования.

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

Иными словами, у интерфейсов есть куча плюсов, но есть и некоторые минусы. Во-первых, использование интерфейсов подвержено проблеме циклических ссылок (которую, впрочем можно легко обойти, следуя нескольким простым правилам разработки). Во-вторых, большая часть кода VCL и RTL написана в те времена, когда никакой поддержки интерфейсов в Delphi не было (историческая справка: изначально интерфейсы были введены в Delphi для поддержки технологии COM, но впоследствии их стали использовать более широко). Соответственно, весь этот код написан на объектах. И он наследуется и в современные версии Delphi. Более того, такой подход используют и сторонние библиотеки, руководствуясь «ну раз так поступает сам разработчик среды, то и мы тоже будем так делать». Итого, у вас может быть проблема состыковки кода с ручным и автоматическим управлением временем жизни. К сожалению, обычные интерфейсы в Delphi нельзя сделать «чистыми» (т.е. без обвеса автоматического управления). Вы можете добиться этого обходным путём, но это неудобно без поддержки со стороны языка.

Некоторые считают, что уже упомянутые плюсы интерфейсов не перевешивают их минусы. Возможно это и так. Но у интерфейсов есть одно существенное свойство, к которому я подвожу: интерфейс позволяет описывать некоторые желательные свойства, которыми могут обладать сущности. Интерфейс, по сути, является не только синтаксической, но и семантической конструкцией, используемой для спецификации услуг, предоставляемых компонентом. Интерфейс определяет границу взаимодействия между компонентами, определяя определённую абстракцию, которую осуществляет реализующая сторона. Интерфейс — это «договор», который обязуется выполнить компонент.

К чему это я? Когда мы говорим про ООП — мы говорим в первую очередь про наследование. Это именно то свойство (из наследования, абстракции, инкапсуляции и полиморфизма), которое наиболее ярко определяет ООП. Когда нас учат ООП, нас учат моделировать мир иерархиями объектов: объекты в программе отражают сущности реального мира. Но в реальном мире не существует понятия наследования (кроме нескольких узких областей). В реальной жизни стул не является наследником мебели. И уж тем более студент не является множественным наследником «человек», «рабочий», «музыкант».

Я не говорю, что наследование — это зло. Это удобная концепция, лучше всего раскрывающаяся для повторного использования кода (если у вас есть класс, вы можете наследоваться от него, чтобы сохранить его функциональность и внести некоторые изменения). Впрочем, при необходимости в качестве альтернативы наследованию можно выбрать агрегацию/делегирование.

Проблема в том, что унаследовавшись от одного предка, класс уже не может наследоваться от других. Изменение предка становится опасным. Зачастую правильное использование private и protected требует от программиста неслабых телепатических способностей: что может понадобится нашим наследникам, а что нет?

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

Если же вместо объектов вы будете оперировать интерфейсами, то ваш код будет более приближен к реальному миру. У вас может быть «студент», и он не будет наследоваться от «человека», но он будет иметь имя, т.к. студент одновременно является и «человеком». И если кому-то нужен «студент», то ему совершенно не обязательно наследоваться от «человека», «млекопитающего» или ещё более низкого класса, если его всего-лишь интересуют университет, курс и группа «студента». Конечно, вы можете и должны использовать объекты и наследование при реализации интерфейсов: в конце концов, наследование — удобный способ повторного использования кода. Иными словами, суммируя мысль: объекты — язык описания реализации, интерфейсы — язык описания реального мира.

Бонус: секция published

В заключение — несколько слов о секции published . Эта секция введена в Delphi для работы встроенного механизма сериализации (к примеру, именно благодаря ему загружаются формы из ресурсов программы). Помещение свойств, методов и полей в секцию published заставит компилятор генерировать мета-информацию (RTTI) для них, что позволит объекту «узнавать о самом себе» во время выполнения. Именно это (генерация RTTI) — основное назначение секции published . Но кроме этого секция published также имеет ту же область видимости, что и public . К примеру, все компоненты любой формы доступны любому коду, т.к. находятся в секции published , т.е. имеют видимость public . Конечно же, это нарушает инкапсуляцию (концепцию «чёрного ящика»). Любой код может проводить произвольные манипуляции с формой, даже приводя её в недопустимое состояние (к примеру, отключив все кнопки на форме).

Поэтому нужно всегда помнить о назначении секции published (генерировать RTTI) и не пытаться манипулировать компонентами формы в обход её публичного интерфейса. С этой точки зрения дизайн Delphi был бы более удачен, если бы секция published имела бы ту же область видимости, что и protected . В настоящее же время наилучшим вариантом будет вообще скрыть форму за фасадом. Например — интерфейсом. или: Подобный подход не только прост в реализации, но и добавляет в код высокую степень полиморфизма: в любой момент вы можете заменить форму на консольный ввод или ответ от удалённого сервера. Для этого достаточно будет заменить реализацию IInputDialog . Любой иной код, который его использует, совершенно не изменится.

Основы создания компонент в среде Delphi

Delphi , Компоненты и Классы , Создание компонент

Начнем с самого простого — с создания объявления класса. Для этого в меню «Component» выбираем пункт «New Component…». В появившемся окне, нужно задать следующие значения: Ancestor Type (класс родитель создаваемого класса), Class Name (имя создаваемого класса), Palette Page (название закладки палитры компонентов, на которую будет установлен данный компонент; если такой закладки не существует, то она будет создана), Unit File Name (имя модуля, в котором будет размещено описание класса).

Итак, создадим потомок TСustomControl’а — TMyClass, в результате чего получим следующие объявление класса:

Ключевое слово class указывает на то, что мы создаем новый класс TMyClass, порождая его от родителя — TCustomControl.

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

Вот пример использования упреждающего объявления класса:

В соответствии с концепцией объектно-ориентированного программирования (инкапсуляцией) в Delphi существуют четыре директивы видимости, предназначенные для сокрытия полей и методов внутри объекта. Итак, первая из них — private. Это сама строгая из всех директив видимости. Поля и методы, объявленные в этой секции, не будут видны во всех классах-потомках. Вторая, менее строгая — protected. Она предназначена для объявления тех полей и методов, которые должны быть видны в классах-потомках. Следующая директива — public, предназначена для объявления элементов, которые будут видны программе или модулю, если они имеют доступ к модулю, в котором объявлен класс. И, наконец, последняя директива — published. Она предназначена для объявления тех свойств, которые должны быть видны в инспекторе объектов на этапе проектирования (design-time).

Вообще, поля — это обычные переменные, инкапсулированные внутри объекта. Объявляются они точно так же, как и в обычном Паскале (смотри пример ниже, FStr — поле типа String). Также полями могут быть и объекты.

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

Реализация методов производится в том же модуле, где и объявление класса, в секции implementation, следующим образом:

Этот пример также хорошо иллюстрирует сущность инкапсуляции. Класс TMyClass инкапсулирует поле FStr и реализацию методов записи — SetStr и чтения — GetStr этого поля.

Для перекрытия методов класса-предка в классе-потомке используется специальная директива override. При перекрывании метода надо учитывать следующее: метод, объявленный в классе-предке в секции private, перекрыть в потомке нельзя; нельзя также перекрывать методы, объявляя их в потомке с меньшей видимостью, чем в классе-предке; при перекрытии методов обработки сообщений директива override не используется. Для того чтобы в теле перекрывающего метода обратиться к методу предка, используется директива inherited. Пример:

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

Методы могут быть виртуальными и динамическими. Они различаются способом решения классической проблемы «время — ресурс, ресурс — время» (в данном случае ресурс — это объем оперативной памяти).

Разница между ними заключается в структуре соответствующих таблиц методов VMT — Virtual Metod Table (таблица виртуальных методов) и DMT — Dynamic Metod Table (таблица динамических методов). Диспетчеризация виртуальных методов происходит быстрее, чем динамических, но с другой стороны, таблица виртуальных методов (VMT) занимает больше места в памяти, чем таблица динамических (DMT). Выбор остается за программистом.

Для определения типа метода (виртуальный или динамический) используются две директивы — virtual и dynamic соответственно. Для указания типа метода после его объявления через точку с запятой указывается одна из этих директив. Вот простой пример:

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

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

Еще одна разновидность методов — это методы обработки сообщений (message-handling metods). Эти методы специально предназначены для обработки сообщений Windows. На них накладываются некоторые ограничения: они обязательно должны быть процедурами, должны иметь один параметр — переменную; при прикрытии таких методов директива override не применяется; также при перекрытии необходимо вызывать метод предка. Объявляются такие методы с помощью директивы message. Вот пример объявления и реализации метода обработки сообщений:

Константа после директивы message определяет сообщение, которое будет обрабатываться этим методом (в данном случае метод будет вызываться каждый раз, когда происходит перемещение курсора мыши над данным объектом). Тип единственного параметра процедуры задает, в каком формате методу будут переданы данные сообщения (например, в этом случае Msg.XPos и Msg.YPos — текущие координаты курсора мыши в области данного компонента). Описание констант и соответствующих типов помещено в модуле Messages.

Классовые методы (или методы класса)

Существует еще одно важное расширение методов — классовые методы. Это методы, которые можно вызывать, не создавая экземпляра объекта (например: TMyClass.MyClassMetod). В теле классового метода нельзя обращаться ни к одному методу или полю данного класса, но можно вызывать конструкторы и другие классовые методы. Обычно такие методы используются для предоставления информации о классе, версии реализации и т. д. Определяются такие методы путем добавления директивы class перед объявлением метода. Вот пример классового метода и его реализации:

Перерегружаемые (overload) методы

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

В данном примере, при вызове пользователем метода SetX, в зависимости от типа передаваемого параметра будет вызван соответствующий метод (для параметра типа String — первый, для Integer — второй).

Пример вызова первого варианта метода:

Пример вызова второго варианта метода:

Удобства, предоставляемые данной возможностью, очевидны, например, в данном случае пользователю не нужно самому конвертировать Integer в String.


Конструкторы и деструкторы

Конструкторы и деструкторы применяются для создания экземпляра класса и его удаления. Конструктор выделяет необходимую память, инициализирует поля, производит первоначальную подготовку объекта к последующему использованию. Конструктор объявляется с помощью директивы constructor и обязательно должен называться — Create. В теле конструктора первым же делом необходимо вызвать конструктор предка. Деструктор освобождает выделенную конструктором память, закрывает используемые файлы, потоки и т. д. Деструктор объявляется с использованием директивы destructor, называться он должен Destroy. В теле деструктора деструктор предка вызывается в самую последнюю очередь. Пример:

Свойства — это второе по важности (после методов) средство обеспечения взаимодействия объекта с окружающим миром. Через свойства обеспечивается доступ к полям объекта, только свойства могут быть видны в инспекторе объектов (Object Inspector) Delphi.

Объявление свойства, чтение и запись.

Вот один из вариантов объявления свойства:

Свойства объявляются с использованием директивы property, за ней следует имя свойства и тип (как объявление переменной в обычном Паскале), затем директива read и указание «источника» чтения (это либо поле такого же типа, как свойство, либо функция без параметров, возвращающая значение того же типа), далее следует директива write с указанием «приемника» записи (поле с тем же типом, что и свойство, либо процедура с одним параметром, того же типа). В нашем случае чтение осуществляется напрямую из поля, а запись через процедуру. Здесь есть одна хитрость. Соответствующее поле не обязательно должно существовать или иметь тот же тип, что и свойство. Это позволяет снизить избыточность хранимой информации и уменьшить расход памяти. Распространенным примером такого подхода является предоставление информации о размере какого-либо массива, например так:

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

property StrCount: Integer read GetStrCount; — свойство только для чтения (read-only)
property StrCount: Integer write SetStrCount; — свойство только для записи (write-only).

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

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

Тип индекса может быть и другим, например строкой.

Для многомерных массивов все точно так же, только вместо одного индекса (и в объявлении методов чтения и записи, и в описании свойства) нужно указывать необходимое число индексов, например — для двумерного массива:

К таким свойствам следует обращаться, как к массивам, например для экземпляра Test класса TMyClass — Test.Str[5].

Этот тип свойств применяется для экономии, когда для нескольких однотипных свойств используется один метод чтения и/или записи. Для этого в объявление свойства вводится директива index и индекс после нее, а методы чтения и записи должны иметь такой же вид как и для случая одномерного массива. Пример:

Как видно из примера, если для свойства определен индекс, совершенно необязательно для чтения или записи использовать метод, поддерживающий индексирование. Методом чтения и записи в качестве индекса (параметр Index, если у них таковой имеется) для каждого свойства будет передаваться то значение, которое указано после директивы index. Ну а в теле метода по полученному значению индекса выясняем, к какому из свойств было осуществлено обращение, и предпринимаем соответствующие действия.

Свойства совсем необязательно объявлять в public или published секциях. Очень часто наоборот бывает удобно объявить свойство в секции protected для последующего переобъявления его в классах-потомках. Но не все так хорошо — нельзя понизить видимость, т. е. если свойство объявлено в классе-предке в секции public, то переобъявив его в потомке в секции protected, невидимым его сделать не получится.

Переобъявляются свойства очень просто, возьмем наш пример с массивом строк:

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

Тип свойства должен совпадать с типом значения, указываемого после default’а.

С этой директивой связано одно распространенное заблуждение — многие считают, что с ее помощью можно инициировать значения свойств. Это неверно. Дело в том, что все значения свойств, установленные программистом для определенного объекта, в инспекторе объектов сохраняются в файл формы (.dfm), который потом компилируется вместе с кодом. При запуске приложения автоматически создаются объекты, заданные на этапе проектирования, и для них загружаются и устанавливаются значения свойств. Это не выгодно, во-первых, потому, что увеличивает размер откомпилированного exe файла, а во-вторых, на все это тратится изрядная доля времени при запуске приложения.

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

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

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

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

На такое свойство можно ссылаться без указания его имени, например для экземпляра MyClass класса TMyClass — MyClass[10] равносильно MyClass.Str[10].

Эта директива предназначена для жесткого определения — сохранять значение свойства в файле формы или нет. Она добавляется в конец определения свойства, и после нее должно идти либо булево (boolean — FALSE или TRUE) значение, либо функция, возвращающая булево значение, либо поле булевого типа. Если булево значение ИСТИНА (TRUE), то сохранение значения свойства происходит, если ЛОЖЬ (FALSE) — нет. Этот механизм может быть полезен, если существует какая-либо избыточность полей и соответствующих им свойств. Возможно совместное использование директив default и stored. Пример (взят из модуля Controls):

События — это единственный для объекта способ спровоцировать какое-либо внешнее действие в ответ на изменение его состояния.

Событие как свойство

В Delphi все события реализуются как свойства определенного типа. Этот тип должен быть предварительно определен. Вот определение самого распространенного в Delphi свойства:

Как видно, это обычная процедура (также может быть использована и функция) с добавлением директивы of object.

Само объявление же события будет выглядеть так:

Вызывается сообщение путем обращения к соответствующему полю как к процедуре или функции. Для предыдущего примера это будет выглядеть так:

Перед вызовом события обязательно нужно проверить, существует ли обработчик этого события — это выполняется с помощью функции Assigned. Если этого не сделать, то при отсутствии обработчика получим ошибку времени выполнения, доступа к памяти. Self — внутри каждого объекта — это указатель на самого себя, который мы и передаем в качестве параметра Sender.

Общепринятые правила именования

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

  • из названия должно быть понятно, для чего предназначен данный класс, метод, поле или свойство;
  • название классов принято называть с T, от type;
  • к названию иногда добавляют инициалы разработчика компонента или часть названия фирмы. Для Васи Иванова это может быть — TviComponent;
  • имена полей принято начинать с F, от Field (поле, область);
  • свойства принято называть точно так же, как и поля (если свойству в соответствие ставится поле), опуская F. Поле FStr — свойство Str;
  • имена методов чтения и записи принято образовывать путем добавления перед именем свойства Get и Set, соответственно. Свойство Str — метод чтения GetStr, метод записи SetStr;
  • именование полей и методов чтения и записи для событий подчиняются тем же правилам, что и для свойств;
  • имена событий принято начинать с On: OnClick, OnMouseMove.

Для того чтобы установить созданный класс в палитру компонентов, в меню «Component» выбираем пункт «Install Component…». В появившемся окне нужно задать следующие значения: Unit file name (имя модуля, в котором содержится описание устанавливаемого класса), Search path (пути через «;» по которым Delphi будет искать указанный модуль), Package file name (путь и имя файла пакета, в который будет добавлен устанавливаемый вами модуль с описанием класса).

Статья Основы создания компонент в среде Delphi раздела Компоненты и Классы Создание компонент может быть полезна для разработчиков на Delphi и FreePascal.

Комментарии и вопросы

:: 2012-11-15 14:05:00 :: re:Основы создания компонент в среде Delphi

В описании перегружаемых методов вместо overload везде написано override.

:: 2012-11-15 14:13:33 :: re:Основы создания компонент в среде Delphi

Вот так бывает запутаешься, в чем разница между override и overload, найдешь статью, чтобы раз и навсегда расставить точки над i, а в ней автор сам тоже все перепутал. Нехорошо.

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

Лучшие IT-решения для бизнеса

Код-Эксперт


  • Автоматизация бизнес процессов
  • Сопровождение 1С
  • Разработка любой сложности, на платформе 1С
  • Создание и продвижение сайтов
  • Софт и ПК для вашей компании

Как мы работаем

1. Обсуждаем проблему по телефону. При наличии удаленного доступа — показываете на экране вашего компьютера.

2. Мы оцениваем работу в рублях, если проект большой, если нет — примерное кол-во часов.

3. Мы выполняем работу.

4. Вы принимаете работу в вашей программе, если есть недочеты мы их исправляем.

5. Мы выставляем счет, вы оплачиваете.

Стоимость работ

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

2. Стоимость работ по категориям:

Услуга Цена Минимальный объем работ
Консультации 900 р/час 1 час, далее по 20 мин.
Обновления 1100 р/час 1 база
Программирование 1400 р/час 1 час, далее по 20 мин.

3. На работы более 10 часов предварительно составляется техническое задание с описанием и стоимостью работ. Работы начинаются после согласования ТЗ с вами.

Техническая поддержка

1. Если вы обнаруживаете какие то ошибки, в ранее принятых работах, в течении 3х месяцев, мы исправляем их бесплатно.

2. Постоянным клиентам любые недочеты, допущенные в наших работах, исправляем бесплатно в течении года.

Программы для управления вашим бизнесом. Продажа 1С.

Мы являемся официальным дилером фирмы 1С, вы можете приобрести у нас различные программные продукты и лицензии. Кроме покупки «коробки» мы поможем вам настроить программу, проконсультируем и сделаем базовые настройки.

  • Автоматизация продаж
  • Бухгалтерский учет
  • Множество других направлений.
  • Помощь в установке и настройке.
  • Тонкая настройка конфигураций под нужды заказчика, разработка новых модулей при отсутствии необходимых функций в стандартной конфигурации.

SMS из вашей 1С

Хотите чтобы клиенты во время узнавали об акциях, скидках? Клиенты не возвращаются? Настройте отправку SMS прямо из 1С!

Наша компания сможет в короткие сроки настроить отправку SMS Вашим клиентам напрямую из 1С. Примеры событий которые можно автоматизировать:

  • Благодарность за покупку и начисление бонусов сразу после очередной покупки.
  • Начисление бонусов на карту в подарок на день рождения\к другому знаменательному или праздничному дню.
  • Извещение о поступлении товара на склад.
  • Истечение срока подарочных бонусов.
  • Извещение о поступлении предоплаты и резервирования товара.
  • Адрес с уточнениями проезда к магазину\офису, номера телефонов.
  • И т.п.

Настройку в 1С можно произвести силами наших специалистов или своих сотрудников. Ознакомится с тарифами можно на странице SMS-тарифов.

  • Гарантия доставки SMS, деньги снимаются только за доставленные SMS.
  • Отдельная тарификация каждого SMS.
  • Пополнение баланса разными способами.
  • Просмотр истории всех отправленных SMS в любой момент.
  • Имя отправителя вместо цифрового номера в телефоне получателя сообщения.

Создание класса

Здравствуйте! Описал класс «Круг» и его потомок «Цилиндр» с функциями по нахождению их площадей. Но загвоздка в том, что данные (радиус, высота), которые мы берем из edit’ов, не знаю как описать, чтобы они относились к данному классу и чтобы они брались точно из edit’ов.
Описание класса:

В этой процедуре нужно указать, что для каждого класса мы берем значения из edit’ов:

Помогите советом, пожалуйста.

2 ответа 2

Если я правильно понимаю вопрос, то вам в ваших классах не хватает свойств, потому некуда вводить радиус и высоту.

Есть как минимум два варианта работы: объявить публичные поля или, если надо больше контроля — объявить свойства.

Обратиться в обоих случаях можно будет таким образом:

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

Что же касается наследования, то это отдельный вопрос.

В вашем случае было бы логично сделать базовый абстрактный класс TShape( фигура ) и уже от него наследовать ваши «TCircle» и «TCylinder». Так как в вашем случае радиус и высота присутствуют в обоих наследниках( «TCircle» и «TCylinder» ), то было бы умно вынести эти поля в базовый класс. И если вы не желаете делать их доступными напрямую из объекта класса, то поместите из в секцию «private». В нем же( в базовом классе ) создавайте абстрактный метод нахождения площадей, который будет реализован в наследниках. ( здесь наблюдается паттерн Шаблонный метод )

Классы в Delphi. Создание класса Инструкция

Как и большинство современных языков программирования Delphi предоставляет среду разработки, в которой при создании программы могут быть реализованы принципы ООП.

Объектно-ориентированное программирование (ООП) – это определенная система правил, при которой данные и процедуры, работающие с этими данными, объединяются в структуры называемые объектами. При этом разновидности таких объектов, их типы, называются классами.

Преимущества ООП

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

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

Создание классов в Delphi

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

Следует знать, что ООП в частности классы в Delphi имеет такие особенности:

  1. Наследование – способность при создании новых объектов класса приобретать ими все характерные особенности своих предков. То есть, вновь созданные объекты могут наследовать все поля, методы, свойства своего родителя.
  2. Инкапсуляция – скрытия данных объекта и методов, работающих с этими данными от пользователя.
  3. Полиморфизм – методы с одинаковыми названиями, но относящиеся к разным объектам и выполняющие различные операции.

Структура программы, в которой происходит создание классов Delphi, будет иметь следующий вид:

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

Илон Маск рекомендует:  Что такое код msession_destroy
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL