Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP_answers (1).docx
Скачиваний:
2
Добавлен:
01.03.2025
Размер:
2.9 Mб
Скачать

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

IInterface = interface

['{00000000-0000-0000-C000-000000000046}']

...

end;

В будущем нашему интерфейсу ITextReader понадобится глобально-уникальный идентификатор. Но как его выбрать так, чтобы он оказался уникальным? Очень просто – нажмите в редакторе кода комбинацию клавиш Ctrl+Shift+G.

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

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

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

const

IID_ITextReader: TGUID = '{DC601962-28E5-4BF7-9583-0CE22B605045}';

Function TestInterface(constIID: TGUID): Boolean;

begin

...

TestInterface(ITextReader);

// эквивалентно

TestInterface(IID_ITextReader);

...

end;

Совместимость интерфейсов и классов

Совместимость интерфейсов подчиняется определенным правилам. Если интерфейс создан расширением уже существующего интерфейса:

IExtendedTextReader = interface(ITextReader)

то интерфейсной переменной базового типа может быть присвоено значение интерфейсной переменной производного типа, но не наоборот. Любая интерфейсная переменная совместима с типом данных IInterface – прародителем всех интерфейсов.

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

Var

Intf: ITextReader; // интерфейсная переменная

Obj: TTextReader; // объектная переменная

Begin

Intf := Obj; // В переменную Intf копируется ссылка на объект Obj

end;

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

ITExtendedTextReader = class(TInterfacedObject, ITextReader, IExtendedTextReader);

Исключением из только что описанного правила является совместимость всех снабженных интерфейсами объектов с интерфейсом IInterface.

Получение интерфейса через другой интерфейс

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

var

Intf: IInterface;

begin

...

With Intf as ITextReader do

Active := True;

...

end;

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

В действительности оператор as преобразуется компилятором в вызов метода QueryInterface:

var

Intf: IInterface;

IntfReader: ITextReader;

...

IntfReader := Intf asITextReader; // Intf.QueryInterface(ITextReader, IntfReader);

Напомним, что метод QueryInterface описан в интерфейсе IInterface и попадает автоматически во все интерфейсы. Стандартная реализация этого метода находится в классе TInterfacedObject.

Представление интерфейса в памяти

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

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

Intf := Obj; // где Intf: ITextReader иObj: TTextReader

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

Механизм вызова метода объекта через интерфейс

Алгоритм вызова метода интерфейса такой же, как алгоритм вызова метода класса. Когда через интерфейсную переменную выполняется вызов метода, Intf.NextLine, реализуется следующий алгоритм:

1) из интерфейсной переменной извлекается адрес (по нему хранится адрес таблицы методов интерфейса);

2) по полученному адресу извлекается адрес таблицы методов интерфейса;

3) на основании порядкового номера метода в интерфейсе из таблицы извлекается адрес соответствующей подпрограммы;

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

Обычными средствами процедурного программирования этот алгоритм реализуется так:

type

TMethodTable = array[0..9999] ofPointer;

TNextLineFunc = function(Self: ITextReader): Boolean;

var

Intf: ITextReader; // интерфейсная переменна

IntfPtr: Pointer; // адрес внутри интерфейсной переменной

TablePtr: ^TMethodTable; // указатель на таблицу методов интерфейса

MethodPtr: Pointer; // указатель на метод

begin

...

IntfPtr := Pointer(Intf); // 1) извлечение адреса из интерфейсной

переменной

TablePtr := Pointer(IntfPtr^); // 2) извлечение адреса таблицы методов

интерфейса

MethodPtr := TablePtr^[3]; // 3) извлечение адреса нужного метода из таблицы

TNextLineFunc(MethodPtr)(Intf); // 4) вызов метода через переходник

...

end.

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

Для доступа к объекту через интерфейс нужна интерфейсная переменная:

var

Intf: ITextReader;

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

var

Obj: TTextReader; // объектная переменная

Intf: ITextReader; // интерфейсная переменная

begin

...

Intf := Obj;

...

end;

После инициализации интерфейсную переменную Intf можно использовать для вызова методов объекта Obj:

Intf.Active := True; // -> Obj.SetActive(True);

Через интерфейсную переменную доступны только те методы и свойства объекта, которые есть в интерфейсе:

Intf.Free; // Ошибка! У интерфейса ITextReadaer нет методаFree.

Obj.Free; // Метод Free можно вызвать только так.

Алгоритм вызова метода интерфейса такой же, как алгоритм вызова метода класса. Когда через интерфейсную переменную выполняется вызов метода, Intf.NextLine, реализуется следующий алгоритм:

1) из интерфейсной переменной извлекается адрес (по нему хранится адрес таблицы методов интерфейса);

2) по полученному адресу извлекается адрес таблицы методов интерфейса;

3) на основании порядкового номера метода в интерфейсе из таблицы извлекается адрес соответствующей подпрограммы;

4) Вызывается код, находящийся по этому адресу. Этот код является переходником от метода интерфейса к методу объекта. Его задача – восстановить из ссылки на интерфейс значение указателя Self (путем вычитания заранее известного значения) и выполнить прямой переход на код метода класса.

Обычными средствами процедурного программирования этот алгоритм реализуется так:

type

TMethodTable = array[0..9999] ofPointer;

TNextLineFunc = function(Self: ITextReader): Boolean;

var

Intf: ITextReader; // интерфейсная переменна

IntfPtr: Pointer; // адрес внутри интерфейсной переменной

TablePtr: ^TMethodTable; // указатель на таблицу методов интерфейса

MethodPtr: Pointer; // указатель на метод

begin

...

IntfPtr := Pointer(Intf); // 1) извлечение адреса из интерфейсной

переменной

TablePtr := Pointer(IntfPtr^); // 2) извлечение адреса таблицы методов

интерфейса

MethodPtr := TablePtr^[3]; // 3) извлечение адреса нужного метода из таблицы

TNextLineFunc(MethodPtr)(Intf); // 4) вызов метода через переходник

...

end.

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

Алгоритм вызова метода интерфейса такой же, как алгоритм вызова метода класса. Когда через интерфейсную переменную выполняется вызов метода, Intf.NextLine, реализуется следующий алгоритм:

1) из интерфейсной переменной извлекается адрес (по нему хранится адрес таблицы методов интерфейса);

2) по полученному адресу извлекается адрес таблицы методов интерфейса;

3) на основании порядкового номера метода в интерфейсе из таблицы извлекается адрес соответствующей подпрограммы;

4) Вызывается код, находящийся по этому адресу. Этот код является переходником от метода интерфейса к методу объекта. Его задача – восстановить из ссылки на интерфейс значение указателя Self (путем вычитания заранее известного значения) и выполнить прямой переход на код метода класса.

Один класс может содержать реализацию нескольких интерфейсов.

Применение интерфейса для доступа к объекту динамически-подключаемой библиотеки

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

Сначала вынесем описание интерфейса ITextReader в отдельный модуль (например, ReaderIntf), чтобы этот модуль в дальнейшем можно было подключить к главной программе:

unit ReadersIntf;

interface

type

ITextReader = interface(IInterface)

...

end;

implementation

end.

Затем удалим описание интерфейса из модуля ReadersUnit, а вместо него подключим модуль ReaderIntf:

unit ReadersUnit;

interface

uses

ReaderIntf;

...

Наконец включим скорректированный модуль ReadersUnit в DLL-библиотеку, которую назовем ReadersLib:

library ReadersLib;

uses

SysUtils, Classes, ReadersUnit;

{$R *.res}

begin

end.

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

А как в программе создавать объекты классов, находящихся в DLL-библиотеке? Ведь в нтерфейсе нет методов для создания объектов! Для этого определим в DLL-библиотеке специальную функцию и экспортируем ее:

Library ReadersLib;

...

Function GetDelimitedReader(constFileName: string; constDelimiter: Char = ';'): ITextReader;

begin

Result := TDelimitedReader.Create(FileName, Delimiter);

end;

exports

GetDelimitedReader;

begin

end.

В главной программе импортируйте функцию GetDelimitedReader, чтобы с ее помощью создавать объекты класса TDelimitedReader:

Program Example;

uses

ReadersIntf;

function GetDelimitedReader(constFileName: string; constDelimiter: Char = ';'): ITextReader;

external 'ReadersLib.dll' name 'GetDelimitedReader';

var

Intf: ITextReader;

begin

Intf := GetDelimitedReader;

...

end.

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

Реализация интерфейса вложенным объектом

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

type

TTextParser = class(TInterfacedObject, ITextReader)

...

FTextReader: ITextReader;

Property TextReader: ITextReader read FTextReader implements ITextReader;

...

end;

Очевидно, что внутренний объект должен быть совместим с реализуемым интерфейсом.

11. Понятие компонента. Понятие визуального программирования. Инструментальные средства визуального компонентного программирования. Современные библиотеки компонентов.

Понятие компонента

Понятие компонента является фундаментальным для среды Delphi. Существует два взгляда на компоненты.

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

ƒ Существует еще и взгляд изнутри, т.е. взгляд из программы на языке Delphi. С этой точки зрения компоненты – это классы, порожденные прямо или косвенно от класса TComponent и имеющие published-свойства. Экземпляры компонентов – это объекты этих классов, существующие в качестве полей формы. Среди опубликованных свойств компонентов обязательно присутствует имя (Name), под которым экземпляр компонента представляется в окне свойств.

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

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

Компонент= состояние (свойства) + поведение (методы) + обратная реакция (события).

Состояние компонента определяется его свойствами. Свойства бывают изменяемые (для чтения и записи) и неизменяемые (только для чтения). Помимо этого, свойства могут получать значения либо на этапе проектирования (design-time), либо только во время выполнения программы (run-time). Первые устанавливаются для каждого компонента в окне свойств и определяют начальное состояние компонента. Во время выполнения приложения эти свойства могут быть изменены программно, соответственно изменится внешний вид и поведение компонента. Вторая группа – это свойства, которые не видны в окне свойств, и управлять которыми можно только программно. С точки зрения языка Delphi различие между этими группами свойств состоит в том, что первые объявлены в секции published, а вторые – в секции public.

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

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

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

Визуальные компоненты (visual components) – это видимые элементы пользовательского интерфейса: кнопки, метки, блоки списков и др. Они выглядят одинаково и на стадии проектирования, и во время работы приложения.

Невизуальные компоненты (non-visual components) – это, так сказать, бойцы невидимого фронта; они работают, но сами на экране не видны. К невизуальным компонентам относятся таймер, компоненты доступа к базам данным и др. В процессе проектирования такие компоненты представляются на форме небольшим значком. Их свойства устанавливаются в уже известном вам окне свойств.

Понятие визуального программирования

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

Визуальное программирование – способ создания программы для ЭВМ путём манипулирования графическими объектами вместо написания её текста.

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

С изобретением визуального программирования, первой ласточкой которого была среда разработки Visual Basic, создание графического пользовательского интерфейса стало под силу даже новичку. В среде Visual Basic можно было быстро создать приложение для операционной системы Windows, в котором были все присущие графическому пользовательскому интерфейсу элементы. Проектирование пользовательского интерфейса упростилось на порядок, однако, для профессиональных программистов язык Basic оказался явно слабоват. Отсутствие в нем контроля типов данных и механизма их расширения оказалось камнем преткновения на пути создания серьезных программ. Аналогичную функциональность предлагают Visual C++, Borland C++ начиная с версии 4, Symantec Visual Cafe, C++ Builder, однако эти среды лишены недостатков Basic.

Все перечисленные среды являются визуализирующими надстройками над обычными языками программирования – например, визуализируемой моделью в Visual Basic и Delphi является окно (форма, диалог) Windows, а не код программы.

Проблему визуализации кода призваны решать графические языки программирования, представителем которых является ДРАКОН. ДРАКОН используется для программирования в ракетно-космической технике и имеет наиболее строгое теоретическое обоснование. Императивная (процедурная) часть языка ДРАКОН опирается на новый метод — двумерное (графическое) структурное программирование. Текстовые управляющие структуры (ключевые слова if, then, else, case, switch, break, while, do, repeat, until, for и т. д.) в двумерном программировании заменяются на управляющую графику.

Слабое место классического структурного программирования и текстового представления алгоритмов и программ заключается в недостатке выразительных средств. Следствием являются ограничения и запреты. В рамках текстового представления управляющих структур устранить эти ограничения и запреты невозможно. По мнению разработчиков языка ДРАКОН, чтобы добиться улучшения, надо перейти к двумерному структурному программированию. Многие ограничения и запреты, неизбежные при текстовом структурном программировании, во многих случаях противоречат здравому смыслу, затрудняют понимание алгоритмов и программ, искажают нормальный ход человеческой мысли. Шампур-метод и язык ДРАКОН устраняют этот недостаток.

ДРАКОН использует два типа элементов: графические фигуры (иконы) и текстовые надписи, расположенные внутри или снаружи икон (текстоэлементы). Таким образом, ДРАКОН имеет два синтаксиса: графический и текстовый. Графический (визуальный) синтаксис охватывает алфавит икон, правила их размещения в поле чертежа и правила связи икон с помощью соединительных линий. Текстовый синтаксис задает алфавит символов, правила их комбинирования и привязку к иконам (привязка необходима потому, что внутри разных икон используются разные типы выражений). В качестве текстовой части может использоваться синтаксис любого из невизуальных языков (С, Java и др.), т.е. ДРАКОН образовывает семейство языков с единым графическим синтаксисом и текстовым синтаксисом, приспособленным под конкретные задачи.

Понятие шампура относится к графическому синтаксису. Шампур – вертикальная линия, соединяющая икону «заголовок» и икону «конец». Между этими иконами обычно помещается несколько других икон. Все они, словно кусочки мяса, оказываются нанизанными на шампур.

Правило шампура: выход иконы «заголовок» и вход иконы «конец» должны лежать на одной вертикали. Если это правило выполняется, дракон-схема становится более упорядоченной, эргономичной, легкой для чтения. И наоборот, нарушение данного правила делает схему корявой, изломанной, неудобной для глаза.

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

Современные библиотеки компонентов

Визуальные средства разработки — как правило, под ними подразумевают средства проектирования интерфейсов или какую-либо CASE-систему для быстрой разработки приложений или SCADA-систему для программирования микроконтроллеров.

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

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

Пожалуй, самыми известными средствами визуального программирования являются Visual Studio от Microsoft, а также Delphi и C++ Builder от Borland. Что ж, остановимся на них чуть подробнее.

Borland Delphi и C++ Builder

Среда Delphi появилась на свет в конце 1995 года. И с каждым годом выходят ее новые версии, в которых постоянно реализуются новые компоненты, мастера и модули, которые в свою очередь оснащают программистов все новыми возможностями. Delphi хорошо подходит для создания как простых приложений, создание которых занимает 2-3 часа, так и для мощных корпоративных проектов, работать в которых могут от десятков до сотен пользователей.

VCL (Visual Component Library) – является довольно богатой библиотекой готовых компонентов, причем она постоянно расширяется с выходом каждой новой версии продукта.

Borland C++ использует такую же библиотеку компонентов, однако разница заключается в том, что в C++ Builder пишут на языке C++, а в Delphi используется объектно-ориентированный Паскаль.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]