Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Delfi, лекции / Зан Классы

.doc
Скачиваний:
16
Добавлен:
28.03.2016
Размер:
515.58 Кб
Скачать

13

Классы

Классами в Delphi называются специальные типы, которые содержат поля, методы и свойства.

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

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

Основные понятия

В основе классов лежат три фундаментальных принципа, которые называются инкапсуляция, наследование и полиморфизм.

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

Класс представляет собой единство трех сущностей - полей, методов и свойств.

Объединение этих сущностей в единое целое и называется инкапсуляцией.

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

Например, класс TForm содержит (инкапсулирует в себе) все необходимое для создания окна Windows-программы, класс TMemo представляет собой полнофункциональный многострочный текстовый редактор, класс TTimer обеспечивает работу программы с таймером и т.д.

Инкапсуляция является мощным средством обмена готовыми к работе программными заготовками.

Библиотека классов Delphi - это, фактически, набор «кирпичиков», созданных программистами Borland для построения программ.

Наследование

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

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

Все классы Delphi порождены от единственного родителя - класса TObject.

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

Следующие два объявления идентичны:

Принцип наследования приводит к созданию ветвящегося дерева классов, постепенно разрастающегося при перемещении от класса TObject к его потомкам.

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

Для примера на рис.1.1 показан небольшой фрагмент дерева классов Delphi.

Класс TPersistent обогащает возможности своего родителя TObject: он «умеет» сохранять данные в файле и получать их из него, в результате это умеют делать и все его потомки. Класс TComponent, в свою очередь, умеет взаимодействовать со средой разработчика и передает это умение своим потомкам. Класс TControl не только способен работать с файлами и средой разработчика, он еще умеет создавать и обслуживать видимые на экране изображения, а его потомок TWinControl может создавать окна Windows-программ и т.д.

Рис.1.1

Полиморфизм

Полиморфизм - это свойство классов решать схожие по смыслу проблемы разными способами.

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

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

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

Это и называется полиморфизмом объектов.

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

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

Поля

Полями называются инкапсулированные в классе данные.

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

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

Фундаментальный принцип инкапсуляции требует обращаться к полям только с помощью методов и свойств класса.

Однако в Delphi разрешается обращаться к полям и напрямую:

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

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

Методы

Инкапсулированные в классе процедуры и функции называются методами.

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

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

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

Например

Такое перекрытие методов называется статическим, так как реализуется компилятором.

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

В Delphi гораздо чаще используется динамическое перекрытие методов на этапе прогона программы.

Для реализации этого метод, перекрываемый в родительском классе, должен объявляться как динамический (с директивой dynamic) или виртуальный (virtual).

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

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

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

Для создания изображения он использует метод Draw с логическим параметром:

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

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

Динамическое связывание в полной мере реализует полиморфизм классов.

Разница между динамическими и виртуальными методами состоит в том, что таблица динамических методов (DMT) содержит адреса только тех методов, которые объявлены с директивой dynamic в данном классе, в то время как таблица VМТ содержит адреса виртуальных методов не только данного класса, но и всех его родителей.

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

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

Пример

Обращение к неперекрытому абстрактному методу вызывает ошибку периода исполнения. Разумеется, в грамотно составленной программе абстрактные методы никогда не вызываются.

Классы, содержащие абстрактные методы, называются абстрактными.

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

В состав любого класса входят два специальных метода - конструктор и деструктор.

У класса TObject эти методы называются Create и Destroy, также они называются в подавляющем большинстве его потомков.

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

Деструктор удаляет объект из кучи.

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

По своей форме конструкторы и деструкторы являются процедурами, но объявляются с помощью зарезервированных слов Constructor и Destructor:

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

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

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

Специально для уничтожения объектов в модуле System определена процедура FreeAndNil, которая не только уничтожает объект, но и помещает в его указатель (напомню, что им является идентификатор объекта) значение NIL:

FreeAndNil(MyObject).

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

Вызов любого метода родительского класса достигается с помощью зарезервированного слова Inherited (унаследованный):

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

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

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

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

В Delphi имеется возможность в рамках одного класса объявлять несколько одноименных методов. Описанный выше механизм перекрытия родительского метода одноименным методом потомка приводит к тому, что потомок «не видит» перекрытый родительский метод и может обращаться к нему лишь с помощью зарезервированного слова Inherited.

В Delphi введено зарезервированное слово overload (перезагрузить), с помощью которого становятся видны одноименные методы как родителя, так и потомка.

Внимание

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

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

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

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

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

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

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

Свойства

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

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

Например

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

Например, мы могли бы написать такие операторы:

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

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

Рассмотрим оператор:

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

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

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

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

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

Секции определяют области видимости элементов описания класса.

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

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

Секция published используется только при разработке нестандартных компонентов.

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

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

Секция private сужает область видимости до минимума: закрытые элементы описания доступны только внутри методов данного класса и подпрограммах, находящихся в том же модуле, где описан класс.

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

Секция protected доступна только методам самого класса, а также любым его потомкам, независимо от того, находятся ли они в том же модуле или нет.

Секция automated используется только для объявления свойств и методов, которые будут добавлены к так называемому интерфейсу OLE-объектов автоматизации; область видимости членов этой секции не ограничена.

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

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

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

Для предыдущего примера допустимо такое объявление:

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

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

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

Приведение типов классов

Любой тип-класс относится к так называемым типам с информацией о типе во время исполнения (RunTime Type Information, RTTI). Экземпляры таких типов компилятор снабжает дополнительной информацией, позволяющей, в частности, на этапе прогона программы проверить принадлежность экземпляра нужному классу и осуществить его приведение.

Для этого используются операторы is и as:

Соседние файлы в папке Delfi, лекции