
- •1. Понятие модуля. Принципы модульного программирования. Понятие объекта как динамического модуля.
- •2. Понятие класса. Понятие метода. Представление метода в виде обычной процедуры. Понятие конструктора и деструктора.
- •3. Понятие свойства. Методы получения и установки значений свойств. Свойства-массивы (в некоторых языках программирования). Индексаторы (в некоторых языках программирования).
- •Информация о типе времени выполнения программы
- •5. Классы в программных модулях. Атрибуты доступа к элементам объектов. Термин «инкапсуляция».
- •Термин «инкапсуляция»
- •Virtual – в базовом классе
- •7. Понятие ссылки на метод объекта (или делегата – в зависимости от языка программирования). Понятие события. Применение ссылок на методы для расширения объектов.
- •8. Понятие метакласса (в некоторых языках программирования). Методы, применяемые к классам. Виртуальные конструкторы (в некоторых языках).
- •Понятие метакласса (в некоторых языках программирования)
- •Виртуальные конструкторы (в некоторых языках)
- •3) Finally – вызвать free.
- •Если глобально-уникальный идентификатор назначается интерфейсу, то он записывается после ключевого слова interface и заключается в квадратные скобки, например:
- •Microsoft Visual Studio
- •Import Network;
- •Var Stat: SocketStat;
- •Var SocketStatCollection: … ;
- •Конструкторы и деструкторы
- •Стандартные конструкторы
- •Создание объектов по значению (на стеке) и по ссылке (в динамической памяти)
- •Операторы new и delete
- •Размещающий оператор new
- •Порядок конструирования и разрушения объектов
- •Вложенные определения классов
- •«Друзья» класса
- •Статические члены класса
- •19. Перегрузка бинарных операторов. Перегрузка унарных операторов. Перегрузка операторов преобразования типа.
- •Индексаторы
- •Механизм вызова событий
- •Создание пользовательских обобщенных коллекций
- •Создание обобщенных интерфейсов
- •Несколько слов о вложенных делегатах
- •25. Понятие итератора в языке c#. Оператор foreach. Оператор yield.
- •И напоследок... Блок finally
- •26. Понятие атрибутов в языке c#. Создание пользовательских атрибутов. Анализ атрибутов во время выполнения программы. Понятие рефлексии (reflection) в языке c#. Сериализация объектов.
- •Что такое метаданные и зачем они нужны?
- •1. Метаданные в .Net обязательны и универсальны.
- •2. Метаданные в .Net общедоступны.
- •3. Метаданные в .Net исчерпывающи.
- •4. Метаданные в .Net расширяемы.
- •5. Метаданные в .Net конструируемы программно.
- •Получение экземпляра класса Type
- •Динамическая загрузка сборок
- •Динамическая загрузка типов
- •Исследование типа
- •Характеристики типа как целого
- •Члены класса
- •Исследование объекта
- •Динамическое создание объекта и вызов методов
- •Создание объекта по его типу
- •Декларативное программирование
- •Новые механизмы абстракции?
- •Динамическое создание типов
- •Роль графов объектов
- •Formatter сериализации
- •XmlSerializer
- •Интерфейсы iFormatter и iRemotingFormatter
- •Точность типов среди форматеров
- •28*. Ооп в языке программирования Smalltalk. Достоинства и недостатки этого языка в сравнениии с языком программирования c#.
3) Finally – вызвать free.
В С++ существует только блок try-catch.
В программе нужно не обрабатывать исключительные ситуации, а защищать ресурсы от них.
Try-except-end нужен на любом верхнем уровне программы.
Для возврата по try используется стек вызова процедур.
Программы, построенные с использованием механизма исключительных ситуаций, обязаны придерживаться строгих правил распределения и освобождения таких ресурсов, как память, файлы, ресурсы ОС.
Ресурсы нуждаются в защите от исключительных ситуаций. Для этого в среде Delphi предусмотрен еще один вариант защищенного блока:
// запрос ресурса
try
// защищаемые операторы, которые используют ресурс
finally
// освобождение ресурса
end;
Особенность этого блока состоит в том, что секция finally...end выполняется всегда независимо от того, происходит исключительная ситуация или нет. Если какой-либо оператор секции try...finally генерирует исключительную ситуацию, то сначала выполняется секция finally...end, называемая секцией завершения (освобождения ресурсов), а затем управление передается внешнему защищенному блоку. Если все защищаемые операторы выполняются без ошибок, то секция завершения тоже работает, но управление передается следующему за ней оператору. Обратите внимание, что секция finally...end не обрабатывает исключительную ситуацию, в ней нет ни средств ее обнаружения, ни средств доступа к объекту исключительной ситуации.
Блок try...finally...end обладает еще одной важной особенностью. Если он помещен в цикл, то вызов из защищенного блока процедуры Break с целью преждевременного выхода из цикла или процедуры Continue с целью перехода на следующую итерацию цикла сначала обеспечивает выполнение секции finally...end, а затем уже выполняется соответствующий переход. Это утверждение справедливо также и для процедуры Exit.
Как показывает практика, подпрограммы часто распределяют сразу несколько ресурсов и используют их вместе. В таких случаях применяются вложенные блоки try...finally...end:
// распределение первого ресурса
try
...
// распределение второго ресурса
try
// использование обоих ресурсов
finally
// освобождение второго ресурса
end;
...
finally
// освобождение первого ресурса
end;
Кроме того, вы успешно можете комбинировать блоки try...finally...end и try...except...end для защиты ресурсов и обработки исключительных ситуаций.
Приемы надежного программирования
Стиль надежного программирования впитал в себя все идеи модульного и структурного стилей, в то же время акцентируя внимание на некоторых специфических аспектах.
Специфика надежного программирования состоит в выборе таких способов программирования, которые повышают надежность программы. В понятие "надежность" входят две составляющие – корректность и устойчивость.
Надежный стиль программирования обеспечивается применением способов надежного программирования. Эти способы представляют собой совокупность приемов программирования. Каждый из них состоит в применении определенных языковых средств и композиций в конкретных ситуациях, алгоритмах, схемах вычислений и представлениях структур данных.
Методы защиты от ошибок. Они позволяют создавать программы, работающие при наличии ошибок.
Ограничение последствий ошибки: программа строится так, чтобы ошибка не искажала ее работу вне того участка, где эта ошибка возникла.
Локализация ошибки: программа содержит процедуры для возобновления ее правильной работы после устранения ошибки.
Дуальное программирование: программа содержит избыточные ветви, позволяющие заменить хотя бы частично неверные ветви в момент возникновения ошибки.
Тестирование программ.
Надежная программа гарантирует правильность получаемых результатов и безотказность программы. Программа должна быть такова, что ее исполнение на каждом из допустимых наборов исходных данных приводит к ожидаемым результатам ее выполнения, не должно возникать непредвиденных ситуаций, например переполнения, нехватки памяти, зацикливаний и т.д.
При создании программы нужно не просто программировать вычисления, а представлять, как они будут выполняться с разными значениями операндов. Один из важнейших принципов надежного программирования – определение области допустимых значений данных на основе анализа и тестирования программы.
Программа, построенная с применением приемов надежного программирования, должна:
сообщать пользователю об области допустимых значений исходных данных при формулировке задачи или при вводе данных;
контролировать значения исходных данных при их вводе, сообщать о невозможности выполнения вычислений для недопустимых значений;
обеспечивать для каждой из подобластей допустимых значений соответствующие ей вычисления;
контролировать промежуточные результаты вычислений, прекращать вычисления или изменять их порядок при обнаружении недопустимых ситуаций.
10. Понятие интерфейса. Описание интерфейса. Поддержка интерфейса классом. Механизм подсчета ссылок в интерфейсах. Расширение интерфейса. Глобально-уникальный идентификатор интерфейса. Совместимость интерфейсов и классов. Получение интерфейса через другой интерфейс. Представление интерфейса в памяти. Механизм вызова метода объекта через интерфейс. Применение интерфейса для доступа к объекту динамически-подключаемой библиотеки.
Замена множественному наследованию.
__AddRef – нижнее подчеркивание, чтобы компилятор сам вызвал метод, когда необходимо.
.NET-способ кодирования интерфейсов: любому интерфейсу м.б. присвоен URL. GUID удобен тем, что его ширина фиксирована.
Методы интерфейса в классе реализуются аналогично методам класса, но без override.
Понятие интерфейса
При программировании нередко возникает необходимость выполнить обращение к объекту, находящемуся в другом загрузочном модуле, например EXE или DLL. Для решения поставленной задачи Microsoft разработала технологию COM (Component Object Model) – компонентную модель объектов. Технология обеспечивает создание программных компонентов – независимо разрабатываемых и поставляемых двоичных модулей. Поскольку объекты различных программ разрабатываются на различных языках программирования, технология COM стандартизирует формат взаимодействия между объектами на уровне двоичного представления в оперативной памяти. Согласно технологии COM взаимодействие между объектами осуществляется посредством так называемых интерфейсов.
Интерфейс – заголовки методов и описания свойств.
Интерфейс = Объект – Реализация.
Интерфейс сам ничего “не помнит” и ничего “не умеет делать”; он является всего лишь "разъемом" для работы с объектом. Объект может поддерживать много интерфейсов и выступать в разных ролях в зависимости от того, через какой интерфейс вы его используете. Совершенно различные по структуре объекты, поддерживающие один и тот же интерфейс, являются взаимозаменяемыми. Не важно, есть у объектов общий предок или нет. В данном случае интерфейс служит их дополнительным общим предком.
Описание интерфейса
По форме объявления интерфейсы похожи на обычные классы, но в отличие от классов: интерфейсы не могут содержать поля; интерфейсы не могут содержать конструкторы и деструкторы; все атрибуты интерфейсов являются общедоступными (public); все методы интерфейсов являются абстрактными (virtual, abstract). Интерфейсам принято давать имена, начинающиеся с буквы I (от англ. Interface).
Не определив интерфейс ITextReader, невозможно разместить класс TTextReader в DLL-библиотеке и обеспечить доступ к нему из EXE-программы. Создавая DLL-библиотеку, мы с помощью оператора uses должны включить модуль ReadersUnit в проект библиотеки. Создавая EXE-программу, мы должны включить модуль ReadersUnit и в нее, чтобы воспользоваться описанием класса TTextReader. Но тогда весь программный код класса попадет внутрь EXE-файла, а это именно то, от чего мы хотим избавиться. Решение проблемы обеспечивается введением понятия интерфейса.
type
ITextReader = interface
// Методы
procedure SetActive(const Active: Boolean);
function GetActive: Boolean;
function GetItem(Index: Integer): string;
// Свойства
property Active: Boolean read GetActive write SetActive;
property Items[Index: Integer]: string read GetItem; default;
end;
Поскольку интерфейс не может содержать поля, все его свойства отображены на его методы.
Поддержка интерфейса классом
Интерфейс бесполезен до тех пор, пока он не реализован. Реализацией интерфейса занимается класс. Если класс реализует интерфейс, то интерфейс может использоваться для доступа к объектам этого класса. При объявлении класса имя реализуемого интерфейса записывается через запятую после имени базового класса:
TTextReader = class(TObject, ITextReader)
Такая запись означает, что класс TTextReader унаследован от класса TObject и реализует интерфейс ITextReader. Класс, реализующий интерфейс, должен содержать код для всех методов интерфейса.
Методы QueryInterface, _AddRef и _Release, которые тоже должны быть реализованы. К счастью, вам нет необходимости ломать голову над реализацией этих методов, поскольку разработчики системы Delphi уже позаботились об этом. Стандартная реализация методов интерфейса IInterface находится в классе TInterfacedObject.
TTextReader = class(TInterfacedObject, ITextReader)
При наличии в интерфейсах нескольких одинаковых функций:
function NetxItem: String;
function IStringIterator.Next := NextItem;
Механизм подсчета ссылок в интерфейсах
Механизм подсчета ссылок на объект предназначен для автоматического уничтожения неиспользуемых объектов. Неиспользуемым считается объект, на который не ссылается ни одна интерфейсная переменная. Подсчет ссылок на объект обеспечивают методы _AddRef и _Release интерфейса IInterface. При копировании значения интерфейсной переменной вызывается метод _AddRef, а при уничтожении интерфейсной переменной – метод _Release. Вызовы этих методов генерируются компилятором автоматически.
var
Intf, Copy: IInterface;
Begin
Copy := Intf; // Copy._Release; Intf._AddRef;
Intf := nil; // Intf._Release;
end; // Copy._Release
Стандартная реализация методов _AddRef и _Release находится в классе TInterfacedObject.
type
TInterfacedObject = class(TObject, IInterface)
...
FRefCount: Integer; // Счетчик ссылок
function_AddRef: Integer; stdcall;
function_Release: Integer; stdcall;
...
end;
function TInterfacedObject._AddRef: Integer;
begin
Result := InterlockedIncrement(FRefCount); // Увеличение счетчика ссылок
end;
function TInterfacedObject._Release: Integer;
begin
Result := InterlockedDecrement(FRefCount); // Уменьшение счетчика ссылок
If Result = 0 then // Если ссылок больше нет, то
Destroy; // уничтожение объекта
end;
Функции InterlockedIncrement и InterlockedDecrement просто увеличивают значение целочисленной переменной на единицу. В отличие от обычного оператора сложения, они обеспечивают атомарное изменение значения переменной, что очень важно для правильной работы многопоточных программ.
var
Obj: TDelimitedReader;
Intf, Copy: ITextReader;
begin
Obj := TDelimitedReader.Create('MyData.del', ';');
Intf := Obj; // Obj._AddRef -> Obj.FRefCount = 1
Copy := Intf; // Obj._AddRef -> Obj.FRefCount = 2
...
Intf := nil; // Obj._Release -> Obj.FRefCount = 1
Copy := nil; // Obj._Release -> Obj.FRefCount = 0 -> Obj.Destroy
Obj.Free; // Ошибка! Объект уже уничтожен и переменная Obj указывает в никуда
end;
Обратите внимание, что объектные переменные не учитываются при подсчете ссылок. Поэтому мы настоятельно рекомендуем избегать смешивания интерфейсных и объектных переменных. Если вы планируете использовать объект через интерфейс, то лучше всего результат работы конструктора сразу присвоить интерфейсной переменной:
var
Intf: ITextReader;
begin
Intf := TDelimitedReader.Create('MyData.del', ';'); // FRefCount = 1
...
Intf := nil; // FRefCount = 0 -> Destroy
end;
Если интерфейс является входным параметром подпрограммы, то при вызове подпрограммы
создается копия интерфейсной переменной с вызовом метода _AddRef:
procedure LoadItems(R: ITextReader);
var
Reader: ITextReader;
Begin
LoadItems(Reader); // Создается копия переменной Reader и вызывается
Reader._AddRef
end;
Копия не создается, если входной параметр описан с ключевым словом const:
procedure LoadItems(const R: ITextReader);
var
Reader: ITextRedaer;
begin
...
LoadItems(Reader); // Копия не создается, метод_AddRef не вызывается
end;
Интерфейсная переменная уничтожается при выходе из области действия переменной, а это
значит, что у нее автоматически вызывается метод _Release:
var
Intf: ITextRedaer;
begin
Intf := TDelimitedReader.Create('MyData.del', ';');
...
end; // Intf._Release
Расширение интерфейса
Новый интерфейс можно создать с нуля, а можно создать путем расширения существующего интерфейса. Во втором случае в описании интерфейса после слова interface указывается имя базового интерфейса:
IExtendedTextReader = interface(ITextReader)
procedure SkipLines(Count: Integer);
end;
Определенный таким образом интерфейс включает все методы и свойства своего предшественника и добавляет к ним свои собственные. Несмотря на синтаксическое сходство с наследованием классов, расширение интерфейсов имеет другой смысл. В классах наследуется реализация, а в интерфейсах просто расширяется набор методов и свойств.
В языке Delphi существует предопределенный интерфейс IInterface, который служит неявным базовым интерфейсом для всех остальных интерфейсов.
ITextReader = interface ~ TextReader = interface(IInterface)
Описание интерфейса IInterface находится в стандартном модуле System:
type
IInterface = interface
['{00000000-0000-0000-C000-000000000046}']
Function QueryInterface(constIID: TGUID; out Obj): HResult; stdcall;
Function _AddRef: Integer; stdcall;
Function _Release: Integer; stdcall;
end;
Методы интерфейса IInterface явно или неявно попадают во все интерфейсы и имеют особое назначение. Метод QueryInterface нужен для того, чтобы, имея некоторый интерфейс, запросить у объекта другой интерфейс. Этот метод автоматически вызывается при преобразовании одних интерфейсов в другие. Метод _AddRef автоматически вызывается при присваивании значения интерфейсной переменной. Метод _Release автоматически вызывается при уничтожении интерфейсной переменной. Последние два метода позволяют организовать подсчет ссылок на объект и автоматическое уничтожение объекта, когда количество ссылок на него становится равным нулю. Вызовы всех трех методов генерируются компилятором автоматически, и вызывать их явно нет необходимости, однако программист должен позаботиться об их реализации.
Глобально-уникальный идентификатор интерфейса
Интерфейс является особым типом данных: он может быть реализован в одной программе, а использоваться из другой. Для этого нужно обеспечить идентификацию интерфейса при межпрограммном взаимодействии. Программный идентификатор интерфейса для этого не подходит – разные программы пишутся разными людьми, а разные люди подчас дают одинаковые имена своим творениям. Поэтому каждому интерфейсу выдается своеобразный «паспорт» — глобально-уникальный идентификатор (Globally Unique Identifier — GUID). Глобально-уникальный идентификатор – это16-ти байтовое число, представленное в виде заключенной в фигурные скобки последовательности шестнадцатеричных цифр: {DC601962-28E5-4BF7-9583-0CE22B605045}
В среде Delphi глобально-уникальный идентификатор описывается типом данных TGUID:
type
PGUID = ^TGUID;
TGUID = packed record
D1: Longword;
D2: Word;
D3: Word;
D4: array [0..7] of Byte;
end;
Константы с типом TGUID разрешено инициализировать строковым представлением глобально-уникального идентификатора. Компилятор сам преобразует строку в запись с типом TGUID. Пример:
const
InterfaceID: TGUID = '{DC601962-28E5-4BF7-9583-0CE22B605045}';