FProp1: Integer;
function GetProp2: Integer;
public
property Prop1: Integer read FProp1;
property Prop2: Integer read GetProp2;
end;
В этом классе два свойства Prop1
и Prop2
, значение одного из которых определяется полем FProp1
, а другого — функцией GetProp2
. Оба свойства предназначены только для чтения, но для того эффекта, о котором здесь пойдет речь, это не принципиально: свойства, значения которых можно менять, ведут себя в этом отношении точно так же.
Пусть X
— это переменная типа TSomeClass
. Легко убедиться, что компилятор допускает получение адреса свойства Prop1
, т.е. конструкция вида @X.Prop1
считается допустимой. Результатом выполнении этого оператора станет указатель на поле FProp1
. А вот конструкцию @X.Prop2
компилятор не допускает, выдаёт ошибку Variable required.
Ошибкой компилятора здесь является то, что он допускает получение адреса в первом случае, т.е. для свойства, значение которой берется из переменной. Это грубейшее нарушение принципа инкапсуляции, лежащего в основе объектно-ориентированного программирования, причем сразу по двум причинам. Во-первых, пользователь класса не должен видеть его внутреннюю реализацию, а здесь пользователь может определить, как читается свойство, по возможности применения оператора @
к нему. Во-вторых, пользователь класса должен взаимодействовать с ним строго через предоставленный интерфейс, а у нас получается, что, узнав адрес поля FProp1
, пользователь сможет менять его значение в обход предусмотренных для этого в классе механизмов.
К счастью, ситуации, в которых эта недоработка компилятора могла бы принести пользу, крайне редки. Но если вы все-таки столкнулись с такой ситуацией, настоятельно рекомендуем не поддаваться соблазну и искать другие способы решения проблемы. Если класс, к полю которого вы хотите получить доступ таким образом, написан вами, то это веский повод пересмотреть внешний интерфейс класса, т.к. при его проектировании скорее всего, были допущены серьезные ошибки. Если это «чужой» класс, подумайте о том, что в следующей версии этого класса автор может изменить реализация свойства, и тогда ваш код откажется компилироваться.
3.4.10. Невозможность использования некоторых свойств оконного компонента в деструкторе
Проблема, о которой пойдет речь в этом разделе, гораздо шире, чем это явствует из заголовка. Однако наиболее ярко она проявится именно в этом случае. Поэтому мы начнем именно с этой ситуации, а потом рассмотрим проблему более широко.
Проблему демонстрирует пример ParentWnd на компакт-диске. В нем создан компонент TWrongCombo
, наследник TComboBox
. Листинг 5.67 содержит код компонента.
Листинг 3.63. Компонент TWrongCombo
type
TWrongCombo = class(TComboBox)
public
destructor Destroy; override;
procedure AddItem(const Title: string);
end;
destructor TWrongCombo.Destroy;
var
I: Integer;
begin
for I := 0 to Items.Count - 1 do
if Assigned(Items.Objects[I]) then
Dispose(PDateTime(Items.Objects(I]));
inherited;
end;
procedure TWrongCombo.AddItem(const Title: string);
var
P: PDateTime;
begin
New(P);
P^ := Now;
Items.AddObject(Title, TObject(P));
end;
Класс TWrongCombo
с каждым элементом, добавленным с помощью метода AddItem
, связывает значение типа TDateTime
, хранящее время добавления элемента. В разд. 3.4.6 мы уже познакомились с возможностью связывания данных с элементом списка с помощью свойства Items.Objects
. Но так мы можем связать с элементом только 4-байтное значение, а тип TDateTime
занимает 8 байтов. Поэтому значение TDateTime
мы будем хранить в динамической памяти, а с элементом свяжем указатель на него.
Раз мы выделили динамическую память, ее нужно освободить при удалении компонента. Наиболее подходящим местом для этого кажется деструктор, и именно в нем помещен код освобождения выделенной памяти.
Теперь попробуем воспользоваться компонентом. На главной форме программы ParentWnd
находится кнопка Wrong Combo, при нажатии на которую создается компонент типа TWrongCombo
(листинг 3.64).
Листинг 3.64. Реакция на кнопку Wrong Combo
procedure TForm1.BtnWrongComboClick(Sender: TObject);
begin
if FWrongCombo = nil then
begin
FWrongCombo := TWrongCombo.Create(Self);
FWrongCombo.Left := 10;
FWrongCombo.Top := 10;
FWrongCombo.Parent := Self;
FWrongCombo.AddItem('One');
FWrongCombo.AddItem('Two');
Читать дальше
Конец ознакомительного отрывка
Купить книгу