Implementation — Ключевое слово Delphi


Содержание

Implementation

Дата добавления: 2013-12-23 ; просмотров: 1830 ; Нарушение авторских прав

Interface

СТРУКТУРА МОДУЛЯ

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

unit Unit4;

Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs;

Модуль начинается с ключевого слова unit, после которого пишется имя модуля. Оно совпадает с именем файла, в которым вы сохранили свой модуль.

Текст модуля состоит из двух основных разделов: interfaceоткрытый(внешний) интерфейс модуля, и implementationреализация модуля. Все, что помещается непосредственно в раздел interface(типы, переменные, константы, функции, процедуры), может быть использовано другими модулями программы. Все, что помещается в раздел implementation— внутреннее дело модуля. Внешние модули не могут видеть типы, переменные, константы, функции и процедуры, размещенные в разделе реализации.

В разделе interfaceпосле предложения uses,содержащего список подключаемых модулей, вы можете видеть заготовку объявления класса вашей формы, подготовленную Delphi. Имя класса вашей формы — TForml.Класс содержит два раздела:

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

После завершения объявления класса формы вы можете видеть строки

Это объявляется переменная Formlкласса TForml, т.е. объявляется ваша

форма как объекта класса TForml. Затем следует пока пустой раздел реализации implementation.Раздел implementation включает в себя реализацию модуля. Основное тело модуля составляет коды, реализующие объявленные функции и процедуры.

В данном примере раздел implementation содержит директиву <$R>Директива компилятора <$R>указывает файлы ресурсов (.DFM, .RES), которые должны быть включены в выполняемый модуль или в библиотеку. Указанный файл должен быть файлом ресурсов Windows. По умолчанию расширение файлов ресурсов – файл с расширением .RES. В процессе компоновки компилированной программы или библиотеки файлы, указанные в директивах <$R>, копируются в выполняемый модуль. Компоновщик Delphi ищет эти файлы в том каталоге, в котором расположен модуль, содержащий директиву <$R>.

При генерации кода модуля, содержащего форму, Delphi автоматически включает в файл .pas директиву <$R *.DFM>, обеспечивающую компоновку файлов ресурсов форм. Эту директиву нельзя удалять из текста модуля, так как в противном случае загрузочный модуль не будет создан и сгенерируется исключение EResNotFound.

Существуют еще 2 необязательных раздела: initialization – включает в себя операторы, выполняющиеся только 1 раз при первом обращении программы к данному модулю. В этом разделе могут быть операторы, производящие начальную настройку модуля. Если в программе есть несколько модулей, содержащих разделы initialization, то последовательность выполнения операторов этих разделов определяется последовательностью указания модулей в операторах uses. Например если в головной программе имеется оператор uses unit1,unit2, unit3 , а модуле init1 имеется оператор uses unit3, то последовательность выполнения разделов initialization будет следующей: unit3, unit1,unit2.

Пример применения такого раздела может быть перенос кода головной программы

В раздел initialization какого-либо из объявленных модулей.

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

Заметки о Pascal, Delphi и Lazarus

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

пятница, 18 марта 2011 г.

Структура и синтаксис модулей

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

Файл модуля начинается заголовком, за которым следует ключевое слово interface. За ключевым словом interface следует раздел подключения модулей, который определяет зависимости модуля. Затем следует секция implementation, за которой идут необязательные секции initialization и finalization. Скелет файла с программным кодом модуля выглядит следующим образом:

Модуль должен оканчиваться зарезервированным словом end с точкой.

Заголовок модуля

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

может оказаться в файле с именем MainForm.pas, а файл, содержащий скомпилированный модуль должен быть MainForm.dcu. Имена модулей должны быть уникальны внутри проекта. Даже в том случае, когда файлы находятся в разных директориях, два модуля с одинаковыми именами не могут быть подключены в программе.

Секция Interface

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

Объявления процедур и функций в секции interface содержат только их заголовки, то есть имя подпрограммы, параметры и тип возвращаемого значения (для функций). Блок, содержащий исполняемый код для процедур или функций следует в секции implementation. То есть объявления процедур и функций в секции interface работают как упреждающие объявления.

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

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

Секция Implementation

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

Кроме определения публичных процедур и функций, в секции implementation могут быть объявлены константы, типы (включая классы), переменные, процедуры и функции, которые являются частными (private) для модуля. То есть, в отличие от секции interface, сущности, объявленные в секции implementation недоступны снаружи модулей.

Секция implementation может включать свой собственный раздел подключения модулей, который должен следовать сразу за зарезервированным словом implementation. Идентификаторы, объявленные внутри секции implementation доступны для использования только непосредственно в секции implementation. Вы не можете ссылаться на эти идентификаторы из секции interface.

Секция Initialization

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

Для модулей, указанных в разделе подключения модулей секции interface, инструкции секций initialization модулей, подключенных к клиентам, выполняются в том же порядке, в котором они следуют в разделе подключения клиентов.

Устаревший синтаксис «begin . end.» все еще функционирует. Фактически зарезервированное слово «begin» может быть использовано вместо initialization, за которым может следовать произвольное количество инструкций (0 или больше). В программном коде, в котором используется устаревший синтаксис «begin . end.» Нельзя определить секцию finalization. В этом случае финализация осуществляется назначением процедуры переменной /ExitProc. Этот метод не рекомендован для развивающихся приложений, но вы можете встретить его в старом программном коде.

Секция Finalization

Секция finalization необязательна и может быть включена в модуль только в том случае, если он имеет секцию initialization. Секция finalization начинается зарезервированным словом finalization и продолжается до конца модуля. Она содержит инструкции, которые выполняются в момент завершения основного приложения (за исключением тех случаев, когда для завершения использована процедура Halt). Используйте секцию finalization для освобождения ресурсов, выделенных в секции initialization.

Секции Finalization выполняются в порядке, обратном выполнению секций initialization. Например, если ваше приложение инициализирует модули A, B, C (в таком же порядке), финализироваться они будут в порядке C, B, A.

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

Связи модулей и раздел подключения

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

  • Секцию interface модуля
  • Секцию implementation модуля

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

Модули System и SysInit подключаются автоматически каждым приложением и не могут быть явно перечислены в разделе подключения. (Модуль System реализует подпрограммы ввода/вывода, работу со строками, операции с плавающей точкой, динамическое выделение памяти и так далее). Другие модули стандартных библиотек, например, SysUtils должны явно прописываться в разделе подключения. В большинстве случаев все необходимые модули включаются в раздел подключения средой разработки, по мере того, как вы добавляете или удаляете модули из своего проекта.

Чувствительность к регистру: при объявлении модулей и в разделах подключения модулей имена модулей должны совпадать с именами файлов модулей по регистру. В других случаях (при обращении к идентификаторам) имена модулей не чувствительны к регистру. Чтобы избежать проблем со связями модулей ссылайтесь явно на файл модуля: uses MyUnit in «myunit.pas»;

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

Синтаксис раздела подключения модулей

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

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

Используйте слово in после имени модуля в тех случаях, когда вам нужно указать файл с программным кодом модуля. Поскольку IDE ожидает, что имена модулей совпадают по регистру с их именами в файлах, в которых хранятся эти модули, обычно нет необходимости делать это. Использование in необходимо только когда местонахождение файла с программным кодом неясно. Например, когда:

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

Компилятор полагается на конструкцию in . для того, чтобы определить, какие модуля являются частью проекта. Только те модули, которые указаны в разделе подключения в файле проекта (.dpr) с указанием имени файла могут считаться частью проекта. Остальные модули в разделе подключения используются проектом, но не принадлежат ему. Это различие не играет роли при компиляции, но влияет на инструментарий IDE, например, на Project Manager.

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

Множественные и косвенные связи модулей

Порядок, в котором модули следуют в разделе подключения, определяют порядок их инициализации и влияют на то, как компилятор выполняет поиск идентификаторов. Если два модуля объявляют переменные, константы, типы, процедуры или функции с одинаковыми именами, компилятор использует одну из модуля, указанного последним в разделе подключения. (Для доступа к идентификатору из другого модуля необходимо указать спецификатор: UnitName.Identifier.)

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

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

В этом примере Prog явно зависит от Unit2, который явно зависит от Unit1. То есть Prog косвенно зависит от Unit1. Поскольку Unit1 не указан в разделе подключения Prog, идентификаторы, объявленные в Unit1, недоступны для Prog.

Для компиляции клиентского модуля компилятору необходимо найти все модули, от которых явно или косвенно зависит клиент. До тех пор, пока программный код этих модулей не меняется, компилятору достаточно файлов .dcu.

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

Циклические ссылки на модули

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

Илон Маск рекомендует:  Введение в XML

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

Однако два модуля могут корректно ссылаться друг на друга, если одна из ссылок идет через секцию implementation:

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

Особенности работы с интерфейсами в Delphi

Объектно-ориентированное программирование (ООП), помимо понятия класса, предусматривает также фундаментальное понятие интерфейса.

Что же такое интерфейс и каковы особенности работы с ним в языке программирования Delphi?

Интерфейс — семантическая и синтаксическая конструкция в коде программы, используемая для специфицирования услуг, предоставляемых классом или компонентом (Википедия).

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

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

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

Для объявления интерфейса в Delphi служит ключевое слово interface. Это тоже самое ключевое слово, что определяет секцию модуля, к которой возможно обратиться из вне (между ключевыми словами interface и implementation). Однако при объявлении интерфейса применяется другой синтаксис, схожий с объявлением классов.

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

Дело в том, что Delphi интерфейсы изначально были введены для поддержки технологии COM. Поэтому интерфейс IInterface, который в Delphi является предком для всех других интерфейсов (своего рода аналог TObject), уже содержит в себе три базовых метода по работе с этой технологией: QueryInterface, _AddRef, _Release. Как результат, если класс реализует любой интерфейс, то он должен в обязательном порядке реализовать и эти методы. Даже, если этот класс не предназначен для работы COM.

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

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

Всё это привело к тому, что, несмотря на все возможности предоставляемые интерфейсами, их практическое применение в Delphi почти не вышло за пределы работы с COM.

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

Поэтому, многие Delphi программисты до сих пор, по факту, лишены мощного и гибкого инструмента для разработки архитектуры приложений.

Implementation — Ключевое слово Delphi

Жаль, что нет, спасибо за ответ!

Такие сущности как интерфейсы существуют в любом уважающем себя ОО-языке, в Delphi в т.ч. Но еще раз повторю, они тут ни причем. Спасибо еще раз!

Добавлено 04.03.09, 13:33

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

Implementation — Ключевое слово Delphi

Создание методов с помощью визуальных средств

Более сложные методы и управляющие элементы

Информация периода выполнения. Программа CONTROL3

      1. Обзор
      2. Чтобы полностью понять и почувствовать все преимущества Delphi, Вам нужно хорошо изучить язык Object Pascal. И хотя возможности визуальной части Delphi чрезвычайно богаты, хорошим программистом может стать только тот, кто хорошо разбирается в технике ручного написания кода .

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

    1. Создание методов с помощью визуальных средств
    2. В предыдущем уроке Вы видели, что синтаксический “скелет” метода может быть сгенерирован с помощью визуальных средств. Для этого, напомним, нужно в Инспекторе Объектов дважды щелкнуть мышкой на пустой строчке напротив названия интересующего Вас события в требуемом компоненте. Заметим, если эта строчка не пуста, то двойной щелчок на ней просто переместит Вас в окне Редактора Кода в то место, где находится данный метод.

      Для более глубокого понимания дальнейшего изложения кратко остановимся на концепции объектно-ориентированного программирования. Для начала определим базовое понятие объектно-ориентированного программирования — класс. Класс — это категория объектов, обладающих одинаковыми свойствами и поведением. При этом объект представляет собой просто экземпляр какого-либо класса. Например, в Delphi тип “форма” (окно) является классом, а переменная этого типа — объектом. Метод — это процедура, которая определена как часть класса и инкапсулирована (содержится) в нем. Методы манипулируют полями и свойствами классов (хотя могут работать и с любыми переменными) и имеют автоматический доступ к любым полям и методам своего класса. Доступ к полям и методам других классов зависит от уровня “защищенности” этих полей и методов. Пока же для нас важно то, что методы можно создавать как визуальными средствами, так и путем написания кода вручную.

      Давайте рассмотрим процесс создания программы CONTROL1, которая поможет нам изучить технику написания методов в Delphi.

      Для создания программы CONTROL1 поместите с помощью мышки компонент Edit (находится на страничке “Standard” Палитры Компонентов) на форму. После этого ваша форма будет иметь вид, показанный на Рис. 8-A.

      Теперь перейдите в Object Inspector, выберите страничку “Events” и дважды щелкните в пустой строчке напротив события OnDblClick , как показано на Рис. 8-B . После этого в активизировавшемся окне Редактора Вы увидите сгенерированный “скелет” метода Edit1DblClick , являющегося реакцией на событие OnDblClick :

      procedure TForm1.Edit1DblClick(Sender: TObject);

      После генерации процедуры Вы можете оставить ее имя таким, каким “установил” Delphi, или изменить его на любое другое (для этого просто введите новое имя в указанной выше строке Инспектора Объектов справа от требуемого события и нажмите Enter).

      Теперь в окне Редактора Кода введите смысловую часть метода:

      procedure TForm1.Edit1DblClick(Sender: TObject);

      Edit1.Text:= ‘Вы дважды щелкнули в строке редактирования’;

      Сохраните программу. Во время выполнения дважды щелкните на строке редактирования. Текст в этой строке изменится в соответствии с тем, что мы написали в методе Edit1DblClick : см. Рис. 8-C.

      Рис. -C : Содержимое управляющего элемента TEdit изменяется после двойного щелчка по нему

      Листинг 8-A и Листинг 8-B предоставляют полный код программы CONTROL1.

      Листинг -A : Программа CONTROL1 демонстрирует, как создавать и использовать методы в Delphi.

      Classes, Graphics, Controls,

      Printers, Menus, Forms, StdCtrls;

      procedure Edit1DblClick(Sender: TObject);

      procedure TForm1.Edit1DblClick(Sender: TObject);

      Edit1.Text := ‘Вы дважды щелкнули в строке редактирования’;

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

      Первая строка запрашивает память у операционной системы и создает там объект Form1 , являющийся экземпляром класса TForm1 . Вторая строка указывает объекту Application , “по умолчанию” декларированному в Delphi, чтобы он запустил на выполнение главную форму приложения. В данном месте мы не будем подробно останавливаться на классе TApplication и на автоматически создаваемом его экземпляре — Application . Важно понять, что главное его предназначение — быть неким ядром, управляющим выполнением Вашей программы.

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

      Итак, мы видели, что большинство кода Delphi генерирует автоматически. В большинстве приложений все, что Вам остается сделать — это вставить одну или несколько строк кода, как в методе Edit1DblClick :

      Edit1.Text := ‘Вы дважды щелкнули в строке редактирования’;

      Хотя внешний интерфейс программы CONTROL1 достаточно прост, она (программа) имеет строгую внутреннюю структуру. Каждая программа в Delphi состоит из файла проекта, имеющего расширение .DPR и одного или нескольких модулей, имеющих расширение .PAS . Модуль, в котором содержится главная форма проекта, называется головным. Указанием компилятору о связях между модулями является предложение Uses , которое определяет зависимость модулей.

      Нет никакого функционального различия между модулями, созданными Вам в Редакторе, и модулями, сгенерированными Delphi автоматически. В любом случае модуль подразделяется на три секции:

      Таким образом, “скелет” модуля выглядит следующим образом:

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

      В Delphi процедурам и функциям (а, следовательно, и методам классов) могут передаваться параметры для того, чтобы обеспечить их необходимой для работы информацией. Программа PARAMS демонстрирует, как использовать передачу параметров в методы Delphi. Кроме того, мы узнаем, как:

    3. · создавать свои собственные процедуры

      · добавлять процедуру в класс, формируя метод класса

      · вызывать одну процедуру из другой.

      Программа PARAMS позволяет Вам вводить фразы в строки редактирования. После нажатия кнопки “Вызов процедуры WriteAll” строка из управляющего элемента EditSource скопируется в шесть управляющих элементов — строк редактирования, как показано на Рис. 8-D.

      Далее мы не будем подробно останавливаться на том, как размещать компоненты на форме — считаем, что это Вы уже умеете. После того как Вы разместили на форме семь компонентов Edit , переименуйте с помощью Инспектора Объектов седьмой компонент ( Edit7 ) в EditSource . Положите на форму компонент Button , и в Object Inspector измените его заголовок (свойство Caption ) на “Вызов процедуры WriteAll” (естественно, Вы можете заменить его шрифт, цвет и т.д.).

      После завершения проектирования формы класс TForm1 будет выглядеть следующим образом:

      Следующий шаг состоит в добавлении метода, вызываемого по нажатию пользователем кнопки Button1 . Это, напомним, можно сделать двумя способами:

      Delphi сгенерирует следующую “заготовку”:

      procedure TForm1.Button1Click(Sender: TObject);

      Цель программы PARAMS — научить Вас писать процедуры и передавать в них параметры. В частности, программа PARAMS реагирует на нажатие кнопки Button1 путем вызова процедуры WriteAll и передачи ей в качестве параметра содержимого строки редактирования EditSource ( EditSource.Text ).

      procedure TForm1.Button1Click(Sender: TObject);

      Важно понять, что объект EditSource является экземпляром класса TEdit и, следовательно, имеет свойство Text , содержащее набранный в строке редактирования текст. Как Вы уже, наверное, успели заметить, по умолчанию свойство Text содержит значение, совпадающее со значением имени компонента ( Name ) — в данном случае “EditSource”. Свойство Text Вы, естественно, можете редактировать как в режиме проектирования, так и во время выполнения.

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

      Заголовок этой процедуры выглядит следующим образом:

      procedure TForm1.WriteAll(NewString: String);

      где указано, что передаваемый процедуре параметр NewString должен иметь тип String .

      Вспомним, что задача процедуры WriteAll состоит в копировании содержимого строки редактирования EditSource в шесть других строк редактирования Edit1-Edit6 . Поэтому процедура должна выглядеть следующим образом:

      procedure TForm1.WriteAll(NewString: String);

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

      Возвратимся еще раз к заголовку процедуры. Заголовок состоит из пяти частей:

      procedure TForm1.WriteAll(NewString: String);


      · Первая часть — зарезервированное слово “ procedure ”; пятая часть — концевая точка с запятой “ ; ”. Обе эти части служат определенным синтаксическим целям, а именно: первая информирует компилятор о том, что определен синтаксический блок “процедура”, а вторая указывает на окончание заголовка (собственно говоря, все операторы в Delphi должны заканчиваться точкой с запятой).

      · Вторая часть заголовка — слово “ TForm1 ”, которое квалифицирует то обстоятельство, что данная процедура является методом класса TForm1 .

      · Третья часть заголовка — имя процедуры. Вы можете выбрать его любым, по вашему усмотрению. В данном случае мы назвали процедуру “WriteAll”.

      · Четвертая часть заголовка — параметр. Параметр декларируется внутри скобок и, в свою очередь, состоит из двух частей. Первая часть — имя параметра, вторая — его тип. Эти части разделены двоеточием. Если Вы описываете в процедуре более чем один параметр, нужно разделить их точкой с запятой, например:

      procedure Example(Param1: String; Param2: String);

      После того как Вы создали “вручную” заголовок процедуры, являющейся методом класса, Вы должны включить его в декларацию класса, например, путем копирования (еще раз напомним, что для методов, являющихся откликами на дельфийские события, данное включение производится автоматически) :

      procedure Button1Click(Sender: TObject);

      procedure WriteAll(NewString: String);

      В данном месте нет необходимости оставлять в заголовке метода слово “TForm1”, так как оно уже присутствует в описании класса.

      Листинг 8-C показывает полный текст головного модуля программы PARAMS. Мы не включили сюда файл проекта, поскольку, как уже упоминалось, он практически одинаков для всех программ.

      Листинг -C : Исходный код головного модуля программы PARAMS показывает, как использовать строки редактирования и как передавать параметры.

      WinTypes, WinProcs, Classes,

      Printers, Forms, StdCtrls;

      procedure Button1Click(Sender: TObject);

      procedure WriteAll(NewString: String);

      procedure TForm1.WriteAll(NewString: String);

      procedure TForm1.Button1Click(Sender: TObject);

      При экспериментах с программой PARAMS Вы можете попробовать изменить имена процедур и параметров. Однако, следует помнить, что ряд слов в Delphi являются зарезервированными, и употреблять их в идентификаторах (именах процедур, функций, переменных, типов, констант) не разрешается — компилятор сразу же обнаружит ошибку. К ним относятся такие слова, как “procedure”, “string”, “begin”, “end” и т.п.; полный же список их приведен в on-line справочнике Delphi.

      Не старайтесь запомнить сразу все зарезервированные слова — компилятор “напомнит” Вам о неправильном их использовании выдачей сообщения типа “Identifier expected.” (Ожидался идентификатор, а обнаружено зарезервированное слово).

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

      В программе CONTROL1, рассмотренной в начале урока, был сгенерирован метод, являющийся откликом на событие OnClick строки редактирования Edit1 . Аналогично, можно сгенерировать метод, являющийся реакцией на событие OnDblClick . В программе CONTROL2, имеющейся на диске, расширен список находящихся на форме компонентов и для многих из них определены события OnClick и OnDblClick . Для исследования Вы можете просто скопировать файлы проекта CONTROL1 в новую директорию CONTROL2, изменить имя проекта на CONTROL2.DPR (в этом файле после ключевого слова “ program ” также должно стоять название “CONTROL2”) и добавить компоненты Label , GroupBox , CheckBox , RadioButton , Button на форму (эти компоненты находятся на страничке “Standard” Палитры Компонентов). Ваша форма будет иметь примерно следующий вид — Рис. 8-E.

      Заметим, что Вы должны “положить” компонент GroupBox на форму до того, как Вы добавите компоненты CheckBox и RadioButton , которые, в нашем примере, должны быть “внутри” группового элемента. Иначе, объекты CheckBox1 , CheckBox2 , RadioButton1 и RadioButton2 будут “думать”, что их родителем является форма Form1 и при перемещении GroupBox1 по форме не будут перемещаться вместе с ней. Таким образом, во избежание проблем, компонент, который должен быть “родителем” других компонент ( Panel , GroupBox , Notebook , StringGrid , ScrollBox и т.д.), нужно помещать на форму до помещения на нее его “детей”. Если Вы все же забыли об этом и поместили “родителя” (например, GroupBox ) на форму после размещения на ней его “потомков” (например, CheckBox и RadioButton ) — не отчаивайтесь! Отметьте все необходимые объекты и скопируйте (с удалением) их в буфер обмена с помощью команд меню Edit|Cut. После этого отметьте на форме нужный Вам объект ( GroupBox1 ) и выполните команду меню Edit|Paste. После этого все выделенные Вами ранее объекты будут помещены на форму, и их “родителем” будет GroupBox1 . Описанный механизм является стандартным и может быть использован для всех видимых компонент.

      Выберите объект Label1 . Создайте для него метод, являющийся откликом на событие OnDblClick (см. стр. * ). Введите в метод одну строчку, например:

      procedure TForm1.Label1DblClick(Sender: TObject);

      Edit1.Text := ‘Двойной щелчок на Label1’;

      Запустите программу на выполнение и дважды щелкните мышкой на метке Label1 . Вы увидите, что строка редактирования изменится, и в ней появится текст “Двойной щелчок на Label1”.

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

      Листинг -D : Головной модуль программы CONTROL2.

      Implementing Interfaces: Delphi and C++

      This topic describes the two methods of implementing interfaces in C++ and compares the Delphi method of implementing interfaces. Note that in C++, you can use two different strategies for implementing interfaces:

      • The new __property implements attribute
      • Inheritance

      Contents

      Using __property implements

      The __property implements attribute enables C++ to more easily implement interfaces in a way that is analogous to the use of delegation by Delphi. For more information, see __property implements Support in C++Builder.

      You do not have to reimplement and forward the methods of IUnknown when you implement interfaces in C++. Instead of deriving from TInterfacedObject, use the TCppInterfacedObject template: it handles IUnknown, and you handle the method specific to your interface.

      Comparing Delphi and C++ Implementation of Interfaces

      Here are two examples of implements: One in Delphi and one in C++. The two examples expose the same interface, but the Delphi example is delay-initialized while the C++ one is not.

      C++ Example
      The C++ example is not delay-initialized:

      Using Inheritance

      The following example compares Delphi and C++ (using inheritance) for implementing an interface.

      Here is the Delphi code to write a class that implements interface IPersist to define just a single method, GetClassID:

      As a comparison, here is the C++ code to write the same class when using inheritance:

      ActiveX Helpers

      A typical ActiveX control implements at least 21 interfaces, so implementing interfaces in C++Builder is quite tedious unless you use the __property implements attribute.

      The Delphi ActiveX framework (DAX) provides helper classes (such as TConnectionPoints and TPropertyPageImpl) whose parent classes (in this case, TActiveXControl and TActiveXPropertyPage) are not directly derived from the interfaces implemented by the helpers. Delphi uses the implements directive in property declarations that map to the helpers. It is the implements directive that causes the Delphi compiler record the interface in the RTTI InterfaceTable of the class.

      Similarly, the __property implements attribute causes the C++ compiler to record the interface in the RTTI InterfaceTable of the class. This table is how IUnknown.QueryInterface is implemented, and therefore this table tells the outside world that a particular class implements an interface.

      In the C++ TMyPersist example above, you can see that QueryInterface simply calls TObject.GetInterface:

      TObject.GetInterface is driven off the InterfaceTable in a class’s metadata. If there is no InterfaceTable entry for an interface, then the class must write a raw implementation of QueryInterface to handle each interface it implements rather than let the existing logic in core TObject handle this chore. So this is the reason why COM developers typically use frameworks such as ATL, MFC, BaseCtl, and DAX instead of writing raw IUnknown implementations.

      C++ would not be able to take advantage of ActiveX helpers provided by DAX without some support for __property implements .

      Блог 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 interface implements

      I would expect that the reference counting should work on the outer aggregating object in an interface implementation. If I can refer to another example: Clarity in classes implementing multiple interfaces (alternative to delegation):

      Here is a minimal reproduction of the behaviour:

      If instead of using implements , I implement the interface in the TImplementor class then the destructor runs.

      1 Answer 1

      What is happening here is that you call TContainer.Create and create an instance to an object. But you then assign that instance to an interface reference, the global variable Foo . Because that variable is of type IFoo , the interface delegation means that the implementing object is the instance of TFooImpl and not the instance of TContainer .

      Hence nothing ever takes a reference to the instance of TContainer , its reference count is never increased, and so it is never destroyed.

      I don’t think there’s a very easy way around this. You may be able to use TAggregatedObject but it may not solve your problem. It would force you to declare TContainer.FFoo to be of type TFooImpl which I imagine you do not want to do. Anyhow, here’s what it looks like re-cast that way:

      The documentation does talk about this:

      The class you use to implement the delegated interface should derive from TAggregationObject.

      Initially I could not find any documentation for this TAggregationObject . And finally I realised that it’s actually named TAggregatedObject and is documented.

      TAggregatedObject provides the functionality for an inner object of an aggregate by implementing the IInterface methods to delegate to the controlling IInterface.

      An aggregated object is an object composed of several interfaced objects. Each object implements its own behavior and interfaces, but all the objects share the same reference count, which is that of the controller object. In the container pattern, the controller is the container object.

      TAggregatedObject does not itself support any interfaces. However, as is typical of an aggregate, it does implement the methods of IInterface, which are used by the objects that descend from it. TAggregatedObject, therefore, serves as a base for classes that implement interfaces for creating objects that are part of an aggregate.

      TAggregatedObject is used as a base for classes that create contained objects and connecting objects. Using TAggregatedObject ensures that calls to the IInterface methods delegate to the controlling IInterface of the aggregate.

      The controlling IInterface is specified in the constructor for TAggregatedObject and is indicated by the Controller property.

      In addition there is this from the source code comments:

      TAggregatedObject and TContainedObject are suitable base classes for interfaced objects intended to be aggregated or contained in an outer controlling object. When using the «implements» syntax on an interface property in an outer object class declaration, use these types to implement the inner object.

      Interfaces implemented by aggregated objects on behalf of the controller should not be distinguishable from other interfaces provided by the controller. Aggregated objects must not maintain their own reference count — they must have the same lifetime as their controller. To achieve this, aggregated objects reflect the reference count methods to the controller.

      TAggregatedObject simply reflects QueryInterface calls to its controller. From such an aggregated object, one can obtain any interface that the controller supports, and only interfaces that the controller supports. This is useful for implementing a controller class that uses one or more internal objects to implement the interfaces declared on the controller class. Aggregation promotes implementation sharing across the object hierarchy.

      TAggregatedObject is what most aggregate objects should inherit from, especially when used in conjunction with the «implements» syntax.

      Перечисление модулей в implementation и interface

      • Раздел интерфейса (interface). После оператора unit следующей функцио

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

      этой строкой и оператором implementation данного модуля, доступно извне

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

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

      функции, которые должны быть доступны программе или другим модулям.

      В этом разделе допустимы только объявления (но не реализация!) функций и

      процедур. Сам оператор interface занимает отдельную строку и содержит

      единственное ключевое слово:
      interface

      • Раздел реализации (implementation) следует за разделом интерфейса и начи

      нается оператором implementation. Хотя основное содержимое этой части

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

      можно определять типы данных, константы и переменные, которые будут дос

      тупны только в пределах данного модуля. Сам оператор implementation за

      нимает отдельную строку и содержит единственное ключевое слово:
      implementation

      В разделе uses перечисляются модули, которые будут включены в данную про

      грамму (или в модуль). Например, если в программе FooProg используются функции

      или типы данных из двух модулей — UnitA и UnitB, — то раздел uses этой программы

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

      Модули могут содержать два раздела uses: один — в разделе interface, а другой —

      в разделе implementation.

      Вот пример простейшего модуля:

      Иногда может возникнуть ситуация, когда модуль UnitA использует модуль UnitB,

      а тот в свою очередь — модуль UnitA. Обычно наличие подобных взаимных ссылок

      (circular unit reference) свидетельствует о просчетах на этапе проектирования струк

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

      третий модуль, в состав которого войдут все необходимые функции и процедуры обо

      их модулей. Если же по какимлибо соображениям это невозможно (например, модули

      достаточно велики), переместите один из разделов uses в раздел implementation

      модуля, а другой оставьте в разделе interface. Зачастую это решает проблему.

      Implementation — Ключевое слово Delphi

      Юниты в Delphi служат для вынесения каких-либо классов, процедур, функций, переменных и объектов в отдельные файлы. Это позволяет грамотно организовать структуру проекта в IDE RAD Studio. В очень маленьких проектах, состоящих из 100-500 строк можно обойтись и без создания отдельных юнитов, но если же вы рассчитываете, что ваш проект разрастется хотя бы до пары тысяч строк кода, то без грамотной организации структуры кода обойтись будет просто невозможно. Юниты (unit) позволяют разделить ваш проект на различные «группы», содержащие классы и их реализации. Ведь согласитесь, что будет гораздо удобнее в отдельном файле кода иметь реализацию пользовательского интерфейса (User Interface), в отдельном файле — реализацию логики работы, базовые классы, выполняющие основной функционал, а в отдельный юнит можно вынести, например, какие-либо утилитарные функции. Создать новый юнит в проекте очень просто. Достаточно обратить внимание на Project Manager, в нем выбрать нужный проект (Project1.exe) в группе проектов (ProjectGroup1), кликнуть по нему правой кнопкой мыши и выбрать «Add New -> Unit»:

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

      unit Unit2;

      end.
      Теперь его необходимо сохранить в папку с нашим проектом, и его имя будет соответствовать названию файла, которое мы зададим при сохранении. Кстати говоря, если сохранить файл юнита не прямо в папку с проектом, а например создать в этой папке подпапку, то в Project Manager этот юнит также отобразится внутри папки. Это бывает удобно в крупных проектах, т.к. можно поделить все юниты еще и на какие-то смысловые категории. Выглядеть это будет так:

      Рассмотрим синтаксис нашего нового юнита:
      [cc lang=»delphi»]unit Unit2;

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

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