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

Термин «инкапсуляция»

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

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

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

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

6. Понятие виртуального метода. Перекрытие виртуального метода в производном классе. Абстрактный виртуальный метод. Механизм вызова виртуального метода. Методы обработки сообщений. Термин «полиморфизм».

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

Virtual – в базовом классе

Override – в производном классе

Механизм вызова виртуальных методов – косвенный метод.

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

Динамические методы: находятся в ТВМ по отрицательному смещению. Динамический метод – число + указатель. Пошли в ТВМ, в цикле нашли n, вызвали метод по указателю. Если не нашли число, то переходим в ТДМ базового класса. Если метод не переопределен => его нет в таблице динамических методов, что дает большую экономию.

При обращении к статическому методу компилятор точно знает класс, которому данный метод принадлежит.

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

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

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

Работа виртуальных методов основана на механизме позднего связывания (late binding). В отличие от раннего связывания (early binding), характерного для статических методов, позднее связывание основано на вычислении адреса вызываемого метода при выполнении программы. Адрес метода вычисляется по хранящемуся в каждом объекте описателю класса.

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

Попытка перекрытия с директивой не override, a virtual или dynamic приведет на самом деле к созданию нового одноименного метода.

Механизм вызова виртуального метода

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

Все процедурные переменные с адресами виртуальных методов пронумерованы и хранятся в таблице, называемой таблицей виртуальных методов (VMT — от англ. Virtual Method Table). Такая таблица создается одна для каждого класса объектов, и все объекты этого класса хранят на нее ссылку.

Структура объекта в оперативной памяти:

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

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

1) Через объектную переменную выполняется обращение к занятому объектом блоку памяти;

2) Далее из этого блока извлекается адрес таблицы виртуальных методов (он записан в четырех первых байтах);

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

4) Вызывается код, находящийся по этому адресу.

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

type

TVMT = array[0..9999] of Pointer;

TParseLineFunc = function(Self: TTextReader; constLine: string): Integer;

var

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

ObjectDataPtr: Pointer; // указатель на занимаемый объектом блок памяти

VMTPtr: ^TVMT; // указатель на таблицу виртуальных методов

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

begin

...

ObjectDataPtr := Pointer(Reader); // обращение к данным объекта

VMTPtr := Pointer(ObjectDataPtr^); // извлечение адреса VMT

MethodPtr := VMTPtr^[0]; // извлечение адреса метода из VMT

TParseLineFunc(MethodPtr)(Reader, S); // вызов метода

...

end.

Абстрактный виртуальный метод

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

function ParseLine(constLine: string): Integer; virtual; abstract;

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

Delphi:

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

В последних версиях Delphi также может быть объявлен абстрактным сам класс:

TAbstractClass = class abstract

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

C++:

Абстрактный класс объявляется включением хотя бы одной чистой виртуальной функции

virtual void Abstr ( void ) = 0; // Чистая (пустая) виртуальная функция.

void fun ( void ) { std::cout << "Реализация не будет наследоваться!"; }

~CA () { std::cout << "." << std::endl; } //Вызывается в обр. порядке

class CB : public CA {

public:

CB ( void ) { std::cout << "CB;"; }

void Abstr ( void ){ std::cout << " call function cb.Abstr();"; } //Подменяющая функция.

void fun ( void ){ std::cout << " call function cb.fun()"; }

~CB () {} // Неверно для абстр. кл. ~CB(){ ~CA(); }

};

C#:

Модификатор abstract указывает на то, что класс может быть использован только как базовый класс при наследовании. Абстрактные классы могут содержать абстрактные методы и методы доступа. Создавать экземпляры абстрактного класса нельзя через вызов конструктора, но экземпляр абстрактного класса создается неявно при построении экземпляра производного конкретного класса. Неабстрактный класс, являющийся производным от абстрактного, должен содержать фактические реализации всех наследуемых абстрактных методов и методов доступа. Чтобы указать отсутствие реализации в методе или свойстве, воспользуйтесь модификатором abstract в объявлении метода или свойства. Абстрактный метод - это неявный виртуальный метод. Объявления абстрактных членов (методов, свойств, событий) допускаются только в абстрактных классах и интерфейсах. Поскольку объявление абстрактного метода не предоставляет фактической реализации, тело метода отсутствует, объявление метода просто заканчивается точкой с запятой, аналогично объявлению прототипов:

public abstract void AbstractMethod();

Реализация предоставляется методом переопределения override, который является членом неабстрактного класса.

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

abstract class BaseClass // Abstract class

{

protected int _x = 100;

public abstract void AbstractMethod(); // Abstract method

public abstract int X { get; }

}

DerivedClass : BaseClass

{

public override void AbstractMethod()

{

_x++;

}

public override int X // overriding property

{

get

{

return _x + 10;

}

}

static void Main()

{

DerivedClass o = new DerivedClass();

o.AbstractMethod();

Console.WriteLine("x = {0}", o.X);

}

}

Абстрактный класс с модификатором sealed изменить нельзя, поскольку эти два модификатора имеют взаимоисключающие значения. Модификатор sealed запрещает наследовать классу, в то время как модификатор abstract указывает, что класс обязан иметь производные классы.

Динамические методы

Разновидностью виртуальных методов являются так называемые динамические методы. При их объявлении вместо ключевого слова virtual записывается ключевое слово dynamic.

function ParseLine(constLine: string): Integer; dynamic;

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

По смыслу динамические и виртуальные методы идентичны. Различие состоит только в механизме их вызова.

Методы, объявленные с директивой virtual, вызываются максимально быстро, но платой за это является большой размер системных таблиц, с помощью которых определяются их адреса. Размер этих таблиц начинает сказываться с увеличением числа классов в иерархии.

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

Каждому динамическому методу системой присваивается уникальный индекс. В таблице динамических методов класса хранятся индексы и адреса только тех динамических методов, которые описаны в данном классе. При вызове динамического метода происходит поиск в этой таблице; в случае неудачи просматриваются таблицы DMT всех классов-предков в порядке иерархии и, наконец, класс TObject, где имеется стандартный обработчик вызова динамических методов.

Методы обработки сообщений

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

procedure CMKeyDown(varMsg: TCMKeyDown); message CM_KEYDOWN;

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

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

Логика возникновения подобного трюка совершенно ясна – разработчики Delphi хотели ясный, понятный и читаемый код при разработке визуальных компонент. Как всем известно, при создании окна необходимо задать оконную функцию. То есть некую callback-функцию, которая принимает по ссылке сообщение и возвращает результат. Во всех примерах приложений на чистом WinAPI эта оконная функия состоит из case с перечислением всех сообщений, которое окно хочет обрабатывать и вызова DefWindowProc для остальных сообщений. Для обработки небольшого числа сообщений этот case не страшен. Однако если окно обрабатывает большое количество сообщений и обработчики сообщений содержат ветвления, секции исключений и прочие запутывающие вещи, то отладка, поиск обработки нужного сообщения и чтение подобного превращается в сущий кошмар.

Message методы – часть языка Delphi, эта функциональность реализована уже в TObject, так что создавая любой класс вы уже имеете возможность обрабатывать сообщения. При добавлении message метода в класс этот метод располагается в vmt по адресу, кратному числовому значению сообщения, стоящего после ключевого слова message. Message методы - динамические, ведь обработка сообщения должна быть не только у базового класса, но и у его потомков. Чтобы "послать сообщение", классу нужно сформировать структуру Message и вызвать метод Dispatch нужного класса. Этот метод производит поиск в vmt адрес метода по смещению Msg структуры Message у класса и, если его нет непосредственно у класса, среди message методов его родителей. Если адрес метода не найден (то есть обработка такого сообщения не присутствует ни у одного класса в иерархии), то производится вызов метода DefaultHandler.

Выводы: Во-первых, методы динамические. При этом не обязательно писать override или называть метод тем же именем и даже принимать структуру того же типа, размера и с тем же выравниванием полей, вызов inherited внутри message метода приведет к вызову message метода родителя с передачей туда структуры Message, либо, если у родительских классов нет обработки этого сообщения, к вызову DefaultHandler.

Во-вторых, message методы не обязательно должны принимать именно тип TMessage, описанный в модуле Messages. Достаточно того, чтобы структура имела первое поле типа DWORD, чтобы можно было осуществить переход по адресу, равному числовому значению этого поля. На остальные параметры структуры не налагается ограничений. VCL использует TMessage, поскольку все сообщения Windows, а также пользовательские сообщения CN_BASE + xxx имеют одну (или сходную по размерам) структуру. Однако структуру обязательно нужно передавать по ссылке.

Надо заметить, что на диапазон обрабатываемых сообщений налагается ограничение, а именно от 1 до 49151. Почему 49151? Потому что данный прием был введен прежде всего для обработки сообщений Windows, а в Windows номера сообщений от 1 до WM_USER-1 зарезервированы системой, от WM_USER до $7FFF - для пользовательских сообщений и от WM_APP до $C000-1 (49151) - для сообщений на уровне приложения. От $C000 до $FFFF идет диапазон строковых пользовательских сообщений уровня приложения, создаваемых через RegisterWindowMessage, результат вызова фунции невозможно предсказать на этапе компиляции, поэтому логику обработки подобных сообщений лучше делать в оконной процедуре.

Не всегда в VCL можно решить задачу обработки сообщения исключительно переопределением message метода. Иногда это не приводит ни к какому результату, потому, что помимо обработки сообщения в message методе идет ее обработка и в WndProc. Поэтому иногда все-таки приходится лезть в WndProc, хоть это и нехорошо :).

Однако использование message методов не ограничивается только лишь оконными (то есть обладающими хендлом) компонентами, и класс TControl тому подтверждение – все неоконные контролы способны обрабатывать сообщения. И оконный компонент ответственнен за перенаправление сообщений подчиненным неоконным контролам. Именно message методы сделали это возможным! Более того, их использование также не ограничивается только обработкой сообщений Windows - вы можете с таким же успехом обрабатывать свои сообщения, вооружившись знанием о том, как они работают.

Термин «полиморфизм»

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

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

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