Хранение объектов в памяти. Доступ к свойствам из методов
Вопрос о том, как именно объекты хранятся в памяти, не столь прост. Очевидно, что свойства объекта являются полными аналогами полей записи и хранятся точно так же, как и записи. Но как быть с методами? Ведь это не переменные, а куски исполнимого машинного кода. А если мы создали несколько переменных, принадлежащих к объектному типу, как быть со свойствами и методами?
Для устранения неоднозначности в этом вопросе было введено понятие "экземпляр объекта". Каждая переменная объектного типа является экземпляром соответствующего типа. Для каждого экземпляра создается свой набор свойств, под которые выделяется память. Свойства одного экземпляра никак не связаны со свойствами другого экземпляра. Это и понятно: если в программе есть объект "зубчатое колесо", и мы должны моделировать зубчатую передачу, то свойства у ведущего и ведомого колес будут разными. А вот методы у всех экземпляров совершенно одинаковы. Их хранят в единственной копии для всех экземпляров объекта данного типа (Рис. 5.2).
Иными словами, все экземпляры объекта пользуются одним набором методов, но разными и не связанными между собой наборами свойств.
Рис. 9.2. Хранение объектов в памяти.
Следующая трудность возникает при доступе к свойству объекта. Рассмотрим пример кода:
TYPE TA=CLASS a:WORD; PROCEDURE abc; … PROCEDURE TA.abc; VAR a:BYTE; BEGIN a:=10 END;
Чему будет присваиваться значение 10 при выполнении метода abc – локальной переменной с именем а, описанной внутри этого метода, или же свойству объекта с тем же именем а? Если свойству объекта, то каким образом программа будет знать, о каком из экземпляров объекта идет речь? Метод abc – общий на все экземпляры, в каждом из экземпляров есть свойство а. Какое же из них менять?
Для устранения неопределенности используется ключевое слово SELF – указатель на текущий экземпляр объекта:
PROCEDURE TA.abc; VAR a:BYTE; BEGIN Self.a:=10 { присваивание свойству } a:=20 { присваивание локальной переменной } END;
Self всегда знает, с каким экземпляром объекта идет работа. Откуда? Это очень просто – с каждым экземпляром объекта связано имя объектной переменной. Это же имя ставится перед вызываемым методом, например, x.abc. Имея такую информацию, компилятор будет знать, что внутри метода слово Self в данный момент заменяет собой имя переменной х.
Создаваемые программистом объекты могут включаться в библиотеки и использоваться не только им лично, но и многими другими разработчиками программ. Следовательно, нужно предусмотреть какую-то защиту от неверного использования свойств и методов. С другой стороны, как мы уже видели, прямое изменение значений свойств объекта – операция крайне нежелательная (изменили модуль зуба, а диаметр пересчитать забыли). Эти два соображения привели к появлению принципа инкапсуляции, гласящего: значения свойств не должны меняться напрямую, а только через вызовы соответствующих методов. Рассмотрим два примера программного кода:
Без инкапсуляции
С инкапсуляцией
TYPE TA=СLASS a:WORD;
….
VAR c:TA; BEGIN c:=TA.Create; c.a:=10
TYPE TA=CLASS
a:WORD; PROCEDURE SetA(w:WORD);
….
VAR c:TA; BEGIN c:=TA.Create; c.SetA(10)
В первом случае значение свойства а можно менять напрямую, просто написав с.а:=10. Это явное нарушение принципа инкапсуляции. Во втором случае для изменения значения свойства а введен специальный метод SetA. Внутри этого метода при необходимости будут пересчитываться значения других свойств, зависящих от значения свойства а. Кроме того, можно выполнять проверку корректности присеваемого свойству значения. Если речь идет о свойстве "число зубьев зубчатого колеса", то это число не может быть меньше шести (что, кстати, отражено в самом слове "шестеренка"), иначе не получится плавного зацепления.
Таким образом, инкапсуляция нужна по следующим соображениям:
1. Изменение значения одного из свойств часто должно приводить к изменению значения другого свойства.
Однако ввести метод для присваивания значения свойству еще недостаточно. Ничего же не запрещает горе-программисту не использовать этот метод и по-прежнему писать с.а:=10, что разрушит все наши построения. Поэтому для полного соблюдения принципа инкапсуляции имеется возможность создания скрытых свойств, которые нельзя будет менять напрямую, а только через вызовы методов:
TYPE TA=CLASS PRIVATE a:REAL; PUBLIC b:REAL END;
Перед списком скрытых свойств ставится слово PRIVATE, а перед списком открытых для изменения свойств – слово PUBLIC. В приведенном фрагменте свойство а является скрытым и попытка выполнить оператор вида с.а:=10 приведет к ошибке "Field identifier expected". Однако внутри методов все скрытые свойства видны по-прежнему. Рекомендуется делать скрытыми все свойства объекта