Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
ООП / ООП_Лекции.doc
Скачиваний:
50
Добавлен:
08.06.2015
Размер:
1.03 Mб
Скачать

Клонирование объектов

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

procedure TForml.ButtonlClick(Sender: TObject);

var

AForm: TForm;

begin

// клонирование объекта 'self'

Application.CreateForm(TFormClass(Self.ClassType), AForm);

// перемещение формы и ее отображение

AForm.Left := Left + 50;

AForm.Top := Top +50; .

AForm.Show;

end ;

Для клонирования любого отдельного элемента управления событие OnClick всех этих элементов управления необходимо связать с единственным методом:

procedure TForml.ClickComp(Sender: TObject);

var

ControlText: string;

begin

with TControlClass(Sender.ClassType).Create(self) do begin

Parent := (Sender as TControl).Parent;

Left := (Sender as TControl).Left + 10;

Top := (Sender as TControl).Top + 10;

SetLength(ControlText, 50);

(Sender as TControl).GetTextBuf(PChar(ControlText), 50);

ControlText := PChar(ControlText) + ' *';

SetTextBuf(PChar(ControlText));

end;

end;

Этот метод использует класс объекта Sender, компонент, выбранный пользователем, и вызывает конструктор Create. Однако, чтобы вызвать конструктор Create класса TControl вместо вызова конструктора класса TObject, программа должна привести ссылку на класс к надлежащему типу. После приведения к TControlClass и вызова Create результатом будет объект класса TControl. Этот объект используется внутри оператора with, а программа устанавливает свойства Parent, Left и Тор, используя информацию, извлеченную из элемента управления Sender.

В конце оператора with программа извлекает текст объекта Sender, используя метод GetTextBuf, доступный каждому элементу управления. Свойства Text и Caption не опреде­лены внутри класса TControl. После добавления звездочки в строку программа использует его как новый текст элемента управления, снова вызывая метод SetTextBuf класса TControl.

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

При использовании свойств формы Components всегда можно обратиться к каждому компоненту формы. Однако, если нужен доступ к отдельному компоненту, можно восполь­зоваться методом FindComponent формы владельца вместо сравнения имени каждого компонента с именем искомого компонента. Метод FindComponent класса TComponent по заданному имени компонента в массиве Components и возвращает ссылку на компонент. Если компонент не найден, то возвращается значение nil. Имя компонента (значение его свойства Name) имеет специальную связь с именем соответствующего поля класса формы, ссылающейся на компонент.

Общее правило для новичков: «Никогда не изменяйте значение свойства Name компонента на этапе выполнения, если не вы создали этот компонент при выполнении приложения».

Двукратное освобождение объекта

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

Чтобы понять эту проблему, исследуем, что происходит при вызове метода освобождения Free объекта. В этом методе Delphi вызывает деструктор Destroy, если ссылка на объект не равна nil. Например, следующий код приводит к ошибке:

procedure TForml.ButtonlClick(Sender: TObject);

var

B: TButton;

begin

В := TButton.Create(self);

B.Free;

B.Free;

end;

Как уже упоминалось ранее, если использовать встроенный отладчик Delphi, нажатие этой кнопки может быть действительно опасным. (Конечно, никто преднамеренно не вызывает Free для объекта дважды подряд, но это может возникнуть вследствие работы множества обработчиков событий.) Программа, приведенная ниже, выполнится правильно:

В.Free;

В:= nil;

В.Free; .

Чтобы понять почему, рассмотрим, что происходит при вызове метода Free. Сначала он убеждается, что ссылка на объект не равна nil. В противном случае Free получает информацию о том, что объект по-прежнему существует и использует ссылку на объект для определения его размещения, а затем вызывает Destroy, чтобы освободить память объекта, и исправляет всю соответствующую информацию таблиц виртуальных методов.

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

Если дважды вызвать Free для объекта и не установить ссылку на объект в nil, ссылка на объект по-прежнему будет указывать на адрес памяти, по которому объект существовал до разрушения. Хотя некоторые действия могли бы выполняться (например, выборка значений поля), вызов такого виртуального метода, как Destroy, будет неудачным и приведет к бед­ственным результатам. Установка ссылки на объект в nil предотвращает приложение от попыток использования памяти разрушенного объекта.

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

procedure TForml.Button2Click(Sender: TObject); .

var

Bl, B2: TButton;

begin

// правильный код

Bl := TButton.Create(Self);

B2 := TButton.Create(Bl); // Bl - владелец

B2.Free;

Bl.Free;

end;

Метод Button2Click верен, так как при освобождении объекта, имеющего владельца, метод Destroy вызывает метод RemoveComponent владельца для удаления из списка компонентов:

destructor TComponent.Destroy;

begin

DestroyComponents;

if FOwner <> nil then FOwner.RemoveComponent(Self);

inherited Destroy;

end;

В методе Button2Click вызов B2.Free удаляет объект В2 из списка объектов, принадле­жащих В1, так что В1 не будет пытаться освобождать В2 при работе метода Bl. Destroy. Наоборот - метод Button3Click приводит к ошибке, хотя он кажется очень похожим по образу действия на метод Button2Click:

procedure TForml.Button3Click(Sender: TObject);

var

Bl, B2: TButton;

begin

// ошибочный код

Bl := TButton.Create(Self);

B2 := TButton.Create(Bl); // Bl – владелец

Bl.Free;

B2.Free;

end;

На сей раз мы сначала уничтожаем владельца. Вызов Bl. Destroy уничтожает компоненты, находящиеся в собственности:

procedure TComponent.DestroyComponents;

var

Instance: TComponent;

begin

while FComponents <> nil do

begin

Instance := FComponents.Last; Remove(Instance);

Instance.Destroy;

end;

end;

Когда метод Button3Click вызывает Bl. Free, объект В2 разрушается (но не устанавливает переменную В2 в nil). Следующее обращение к B2.Free приведет к неизбежной ошибке, потому что B2 теперь содержит адрес разрушенного объекта.

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

Соседние файлы в папке ООП