Protected — Директива Delphi

Директивы класса в Delphi

но я не хочу читать какой-то ерунду, лучше почитаю то, что кто-то уже читал и вник в эту тему

private и strict private? Или между protected и strict protected?

Да и фишка в static тоже не особо ясна. Т.е. по сути мы работаем с классом не инициализируя его через TClassName.Create?

Class members with strict private visibility are accessible only within the class in which they are declared. They are not visible to procedures or functions declared within the same unit

Protected — Директива Delphi

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

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

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

Ниже приведено описание класса TPerson, в которое включены директивы управления доступом.

TPerson = class private

FName: TName; // значение свойства Name

FAddress: TAddress; // значение свойства Address

Function GetName: TName;

Function GetAddress: TAddress;

Property Name: TName

Property Address: TAddress

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

Использование разделов класса public, protected, published.

Каждый компонент класса имеет один из атрибутов, определяющих его область видимости (visibility of class members). Это одно из зарезервированных слов private, protected, public,published или automated. Видимость определяет, где и как компонент класса может быть доступен. Атрибут личный (private) определяет наименьшую видимость, защищенный (protected) – среднюю, а общедоступный (public), опубликованный (published) и автоматизированный (automated) – наибольшую.

Если никакой из атрибутов не указан, то по умолчанию предполагается published, если класс компилируется с директивой ;в противном случае такие компоненты имеют атрибут public. Для большей читабельности рекомендуется группировать компоненты, имеющие одинаковую видимость, располагая их в таком порядке: private, protected,public, published и automated. Описание класса тогда имеет следующий вид:

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

Компоненты private, protected и public.

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

Компоненты protectedвидимы везде в том модуле, где объявлен класс, и в любом классе наследнике независимо от того, в каком модуле описан класс наследник. Этот атрибут присваивают тем компонентам, которые хотят сделать доступными только в классах потомках.

Видимость этих компонентов такая же, как и компонентов public. Разница состоит в том, что для опубликованных компонент генерируется информация времени выполнения (runtime type information RTTI). Эта информация позволяет приложению запрашивать поля и свойства объекта какого-либо неизвестного класса динамически. Delphi использует RTTI для доступа к значениям свойств при сохранении и загрузке файлов форм (.DFM), для вывода свойств инспектором объектов и для установки связи (соответствия) между обработчиками событий (event handlers) и специфическими свойствами, называемыми событиями (event).

Допустимыми типами опубликованных свойств являются:

· указатели на методы;

· множество с числом элементов до 32 (порядковые значения 0-31);

· любой вещественный тип, кроме Real48.

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

Класс может иметь опубликованные компоненты, если он компилируется с директивой или является производным от класса, который откомпилирован с этой директивой. Абсолютное большинство классов являются производными от класса TPersistent, который откомпилирован с директивой <$M+>,в связи с чем эту директиву редко приходится использовать.

При добавлении компонентов на форму Delphi помещает их в секцию published (этот атрибут видимости применяется по умолчанию).

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

Num : integer; <Вызываетошибкукомпиляции: Published field ‘Num’ not a class
nor interface type. Поле такого типа надо описывать в другой секции
>

К вопросу о перекрытии описаний в производных классах.

Если в некотором модуле описать, например, объект типа TControl, то защищенные компоненты этого объекта будут недоступны, что естественно. Если теперь описать в этом же модуле класс, производный от TControl, то эти (защищенные) компоненты станут доступны, например:

TMyControl = class (TControl)

Отметим, что доступными становятся все компоненты: поля, методы и свойства.

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

TMyControl = class (TControl)

Компоненты автоматизации (automated members).

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

Опережающие описания и взаимно-зависимые классы (Forward declarations and mutually dependent classes).

Если описание класса заканчивается словом class и символом «;» (точка с запятой), т.е. оно имеет форму

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

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

Опережающее описание позволяет описать взаимно-зависимые классы, Приведем пример:

TFigure = class; // Опережающее описание

TFigure = class // Определяющее описание

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

Использование наследования при создании класса.

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

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

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

Класс, от которого произошло наследование, называется базовым или родительским (англ. base class). Классы, которые произошли от базового, называются потомками,наследниками или производными классами (англ. derived class).

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

Множественное наследование

Основная статья: Множественное наследование

При множественном наследовании у класса может быть более одного предка. В этом случае класс наследует методы всех предков. Достоинства такого подхода в большей гибкости. Множественное наследование реализовано в C++. Из других языков, предоставляющих эту возможность, можно отметить Python и Эйфель. Множественное наследование поддерживается в языке UML.

Множественное наследование — потенциальный источник ошибок, которые могут возникнуть из-за наличия одинаковых имен методов в предках. В языках, которые позиционируются как наследники C++ (Java, C# и др.), от множественного наследования было решено отказаться в пользу интерфейсов. Практически всегда можно обойтись без использования данного механизма. Однако, если такая необходимость все-таки возникла, то, для разрешения конфликтов использования наследованных методов с одинаковыми именами, возможно, например, применить операцию расширения видимости — «::» — для вызова конкретного метода конкретного родителя.

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

Большинство современных объектно-ориентированных языков программирования (C#, Java, Delphi и др.) поддерживают возможность одновременно наследоваться от класса-предка и реализовать методы нескольких интерфейсов одним и тем же классом. Этот механизм позволяет во многом заменить множественное наследование — методы интерфейсов необходимо переопределять явно, что исключает ошибки при наследовании функциональности одинаковых методов различных классов-предков.

Delphi (Object Pascal)

Для использования механизма наследования в Delphi необходимо в объявлении класса справа от слова class указать класс предок:

TAncestor = classprivateprotectedpublic // Виртуальная процедура procedure VirtualProcedure; virtual; abstract; procedure StaticProcedure;end;

TDescendant = class(TAncestor)privateprotectedpublic // Перекрытие виртуальной процедуры procedure VirtualProcedure; override; procedure StaticProcedure;end;

Абсолютно все классы в Delphi являются потомками класса TObject. Если класс-предок не указан, то подразумевается, что новый класс является прямым потомком классаTObject.

Множественное наследование в Delphi частично поддерживается за счёт использования классов-помощников (Сlass Helpers).

Определение инкапсуляции.

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

§ Пользователь может взаимодействовать с объектом только через этот интерфейс. Реализуется с помощью ключевого слова: public.

Илон Маск рекомендует:  TList - Тип Delphi

§ Пользователь не может использовать закрытые данные и методы. Реализуется с помощью ключевых слов: private, protected, internal.

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

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

§ предельная локализация изменений при необходимости таких изменений,

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

Delphi

В Delphi для создания скрытых полей или методов их достаточно объявить в секции private.

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

Определение полиморфизма.

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

Язык программирования поддерживает полиморфизм, если классы с одинаковой спецификацией могут иметь различную реализацию — например, реализация класса может быть изменена в процессе наследования [1] .

Кратко смысл полиморфизма можно выразить фразой: «Один интерфейс, множество реализаций».

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

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

§ внешняя общность проявляется как одинаковый набор методов с одинаковыми именами и сигнатурами (именем методов и типами аргументов и их количеством);

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

Примеры

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

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

Последнее изменение этой страницы: 2020-04-08; Нарушение авторского права страницы

Protected — Директива Delphi

Контакты: о проблемах с регистрацией, почтой и по другим вопросам пишите сюда — alarforum@yandex.ru, проверяйте папку спам! Обязательно пройдите активизацию e-mail.

Форум программистов > Delphi > Общие вопросы Delphi
Protected
Регистрация
Поиск по форуму
Расширенный поиск
К странице.

Здесь нужно купить рекламу за 25 тыс руб в месяц! ) пишите сюда — alarforum@yandex.ru

Ваша тема закрыта, почему это могло произойти? Возможно,
Название темы включает слова — «Помогите», «Спасите», «Срочно»
Название темы не отражает сути вашего вопроса.
Тема исчерпала себя, помните, один вопрос — одна тема
Прочитайте >>>правила

Элементы класса объявленные в секции Protected, доступны только объектам, производным от такого класса.
1) Это как? если я не объявлю в секции Protected элементы родительского класса, то порожденный класс не будет видеть данные родителя?

2) Или же если я не объявлю в секции Protected элементы родительского класса, то доступ к этим элементам будет у всех классов, включая не порожденные? Обьясните пожалуйста.

06.02.2009, 21:17 #1

А про другие секции (private, public, published) вопросов нет?
и видимость в одном модуле?

Элементы размещаются в разных секциях в зависимости от того как ты хочешь чтобы их видели другие.

Delphi ‘private’ clause (directive) does not work

I’m trying to check if my private procedures are really private. But it works the way it shouldn’t.

Please help me, maybe I missed something about how the incapsulation should work.

This code should not work. I guess. But it works.

Thank you. (Delphi-7, Win7 x64).

3 Answers 3

private is for a unit .

With more recent version of Delphi, you can use strict private to get the expected behavior.

The meaning of private is clearly documented:

Private, Protected, and Public Members

A private member is invisible outside of the unit or program where its class is declared. In other words, a private method cannot be called from another module, and a private field or property cannot be read or written to from another module. By placing related class declarations in the same module, you can give the classes access to one another’s private members without making those members more widely accessible. For a member to be visible only inside its class, it needs to be declared strict private.

A protected member is visible anywhere in the module where its class is declared and from any descendent class, regardless of the module where the descendent class appears. A protected method can be called, and a protected field or property read or written to, from the definition of any method belonging to a class that descends from the one where the protected member is declared. Members that are intended for use only in the implementation of derived classes are usually protected.

A public member is visible wherever its class can be referenced.

Strict Visibility Specifiers

In addition to private and protected visibility specifiers, the Delphi compiler supports additional visibility settings with greater access constraints. These settings are strict private and strict protected visibility. These settings can be used in Win32 applications.

Class members with strict private visibility are accessible only within the class in which they are declared. They are not visible to procedures or functions declared within the same unit. Class members with strict protected visibility are visible within the class in which they are declared, and within any descendent class, regardless of where it is declared. Furthermore, when instance members (those declared without the class or class var keywords) are declared strict private or strict protected, they are inaccessible outside of the instance of a class in which they appear. An instance of a class cannot access strict protected or strict protected instance members in other instances of the same class.

Delphi’s traditional private visibility specifier maps to the CLR’s assembly visibility. Delphi’s protected visibility specifier maps to the CLR’s assembly or family visibility.

Note: The word strict is treated as a directive within the context of a class declaration. Within a class declaration you cannot declare a member named ‘strict’, but it is acceptable for use outside of a class declaration.

Your version of Delphi, Delphi 7, does not support the strict specifiers.

Блог 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-7, Win7 x64).

private является для unit .

С более новой версией Delphi, вы можете использовать , strict private чтобы получить ожидаемое поведение.

Частные, Protected и открытые члены

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

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

Общественный элемент виден везде , где можно ссылаться его класс.

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

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

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

Традиционная Деая частная видимость Спецификатор карты для видимости сборок СЬКА. Delphi , защищенная видимость Спецификатор карты для сборки или семьи видимости в СЬКЕ.

Примечание : Слово строго рассматриваются как директива в контексте описания класса. В объявлении класса вы не можете объявить элемент с именем «строгими», но это приемлемо для использования за пределами объявления класса.

Ваша версия Delphi, Delphi 7 не поддерживает strict описатели.

Условная компиляция в Delphi

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

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

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

Теперь нажмите F9 и проверьте, что написано в отладчике в «Events»:

Разберемся с тем, что мы только что написали.

$IFDEF — это директива компилятора;

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

Процедура отправляет строку в отладчик для отображения.

Завершает условную компиляцию, инициированную последней директивой <$IFxxx>(почему не <$IFDEF>— смотрим далее).

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

Где определено условное определение DEBUG? Конкретно в этом случае, символ DEBUG можно найти, если зайти в настройки проекта: Project -> Options ->Delphi Compiler :

Здесь же можно определить и свои собственные символы. Давайте, например, добавим свой символ условной компиляции TEST. Для этого открываем диалоговое окно редактирования символов условной компиляции (жмем кнопку «…» в строке «Conditional defines») и заносим наш символ в список:

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

Теперь можете снова запустить приложения в режиме отладки и посмотреть, что в Events появится строка «TEST IS ON».

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

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

и убедиться, что символ DEBUG выключен, а в окне Events не появится строка «debug is on».

Двигаемся далее. Что делать, если нам необходимо вывести строку не когда символ включен, а именно тогда, когда он выключен? Здесь, опять же, есть варианты. Короткий вариант — воспользоваться директивой противоположной — она называется и код между и выполняется, если символ выключен:

Второй вариант — использование директивы , если в зависимости от состояния символа условной компиляции вам надо выполнять различные участки кода:

Соответственно, нет необходимости далее повторять этот же участок кода с использованием — работать будет, но прямо противоположно.

Также следует обратить внимание на то, что все условные символы оцениваются в Delphi, когда вы выполняете Build проекта. Справка Delphi рекомендует для надежности пользоваться командой Project -> Build All Projects, чтобы быть уверенным, что все символы условной компиляции определены верно.

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

Например, символ условной компиляции VER330 определен для Delphi 10.3 Rio и с его помощью можно определить какой код должен или не должен выполняться, в случае, если версия компилятора Delphi — 33. Например, воспользуемся фичей Delphi 10.3 Rio под названием Inline Variable Declaration:

Сразу может возникнуть вопрос: как сделать так, чтобы приведенный выше код сработал не только в Delphi 10.3 Rio, но и в последующих версиях?
Это можно сделать воспользовавшись, например, такой конструкцией:

Здесь мы уже воспользовались директивой с помощью которой проверили значение константы CompilerVersion, которая находится в модуле System.

Здесь же стоит обратить внимание и на окончание блока — мы использовали директиву , как того требовала Delphi до версии Delphi XE4:

  • для директивы $IFDEF должна быть определена директива $ENDIF
  • для директивы $IF должна быть определена директива $IFEND

В XE4 нам разрешили использовать для закрытия блоков <$IF>, и . Однако, если у вас возникают проблемы при использовании связки и , то вы можете использовать специальную директиву , чтобы потребовать использовать для именно <$IFEND>:

Теперь, если в коде выше использовать директиву $ENDIF, то получим сообщение об ошибке:

Директиву , кстати, можно использовать и с другими константами проекта, например, так:

Так как наша константа Version содержит значение 2, то выполнится участок кода расположенный после . Можете сменить значение константы Version на 1, чтобы убедиться, что выполнится участок кода, где определена переменная s.

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

  1. Использование условной компиляции позволяет нам выполнять тот или иной код, в зависимости от того, какие константы и символы условной компиляции определены или не определены в проекте.
  2. Используя предопредленные символы условной компиляции можно указывать Delphi какой код необходимо выполнить, например, если программа собирается под Android, или, если поддерживается архитектура x64 и т.д.
  3. Директива $IF может использоваться с различными константами, в том числе и определенными самим разработчиком.

При подготовке статьи использовалась следующая информация официальной справки по Delphi:

Fundamental Syntactic Elements (Delphi)

Contents

Fundamental syntactic elements, called tokens, combine to form expressions, declarations, and statements. A statement describes an algorithmic action that can be executed within a program. An expression is a syntactic unit that occurs within a statement and denotes a value. A declaration defines an identifier (such as the name of a function or variable) that can be used in expressions and statements, and, where appropriate, allocates memory for the identifier.

This topic introduces the Delphi language character set, and describes the syntax for declaring:

    >The Delphi Character Set

The Delphi language uses the Unicode character encoding for its character set, including alphabetic and alphanumeric Unicode characters and the underscore. Delphi is not case-sensitive. The space character and control characters (U+0000 through U+001F including U+000D, the return or end-of-line character) are blanks.

The RAD Studio compiler will accept a file encoded in UCS-2 or UCS-4 if the file contains a byte order mark. The speed of compilation may be penalized by the use for formats other than UTF-8, however. All characters in a UCS-4 encoded source file must be representable in UCS-2 without surrogate pairs. UCS-2 encodings with surrogate pairs (including GB18030) are accepted only if the codepage compiler option is specified.

Tokens

On the simplest level, a program is a sequence of tokens delimited by separators. A token is the smallest meaningful unit of text in a program. A separator is either a blank or a comment. Strictly speaking, it is not always necessary to place a separator between two tokens; for example, the code fragment:

Is perfectly legal. Convention and readability, however, dictate that we write this in two lines, as:

Tokens are categorized as special symbols, identifiers, reserved words, directives, numerals, labels, and character strings. A separator can be part of a token only if the token is a character string. Adjacent identifiers, reserved words, numerals, and labels must have one or more separators between them.

Special Symbols

Special symbols are non-alphanumeric characters, or pairs of such characters, that have fixed meanings. The following single characters are special symbols:

The following character pairs are also special symbols:

The following table shows pairs of symbols used in Delphi that have similar meanings (the symbol pairs <> and (* *) are comment characters that are further described in Comments and Compiler Directives):

Илон Маск рекомендует:  Статьи о движках сайтов
Понравилась статья? Поделиться с друзьями:
Кодинг, CSS и SQL
06.02.2009, 21:27 #2