Основы объектно-ориентированного программирования - Бертран Мейер
Шрифт:
Интервал:
Закладка:
Следуя подходу Smalltalk, доступ к атрибуту можно обеспечить только с помощью небольшой экспортированной функции, возвращающей значение атрибута. В примере класса POINT назовем атрибуты internal_x, internal_y и добавим функции abscissa и ordinate. Лаконичный синтаксис Smalltalk допускает присваивание одинаковых имен атрибуту и функции, избавляя от необходимости придумывать специальные имена для атрибутов.
class POINT feature
-- Общедоступные компоненты:
abscissa: REAL is
-- Горизонтальная координата
do Result := internal_x end
ordinate: REAL is
-- Вертикальная координата
do Result := internal_y end
... Другие компоненты аналогичны предыдущей версии ...
feature {NONE}
-- Компоненты недоступные клиентам:
internal_x, internal_y: REAL
end
Этот подход имеет два недостатка:
[x]. Он побуждает авторов классов писать много маленьких функций, аналогичных abscissa и ordinate. Несмотря на то, что такие функции будут очень короткими, автор класса будет тратить на их написание дополнительные усилия, а их присутствие затрудняет восприятие исходного текста.
[x]. Существенное снижение производительности, так как каждое обращение к полю объекта требует вызова функции. Ничего удивительного в том, что объектная технология в некоторых кругах заработала репутацию неэффективной. Можно конечно разработать оптимизирующий компилятор, осуществляющий подстановку вместо вызова функций, но тогда какова роль таких функций?
Подход, обсуждаемый в данной лекции, представляется предпочтительным. Он избавляет от необходимости загромождать исходные тексты многочисленными крошечными функциями и предоставляет возможность экспорта, где это необходимо. Такая практика не мешает скрытию информации, а фактически является непосредственной реализацией этого принципа, как и принципа унифицированного доступа.
Эта методика удовлетворяет требованиям унифицированного доступа (преимущество для клиентов), упрощает восприятие исходных текстов (преимущество для поставщиков) и повышает эффективность (преимущество для всех).
Доступ клиентов к атрибутам
Экспорт атрибута с использованием рассмотренной техники делает его доступным клиентам только для чтения в виде my_point.x. Модификация атрибута путем присваивания не разрешается. Следующая синтаксическая конструкция недопустима для атрибутов (Внимание: недопустимая конструкция - только для иллюстрации.):
my_point.x := 3.7
Действует простое правило. Если attrib является атрибутом, то a.attrib является выражением, а не сущностью. Следовательно, ему нельзя присвоить значение, как нельзя присвоить значение выражению a + b.
Возможность модификации attrib достигается добавлением экспортируемой процедуры вида:
set_attrib (v: G) is
-- Установка значения attrib равным v.
do
attrib := v
end
Вместо этого можно было бы представить следующий синтаксис для разграничения прав доступа пользователей (Внимание: не поддерживаемая нотация. Только для обсуждения.)
class C feature [AM]
...
feature [A]{D, E}
...
здесь A обозначает возможность чтения, а M - модификации. Это устранило бы потребность в частом написании процедур аналогичных set_attrib.
Помимо неоправданных дополнительных языковых сложностей такой подход не слишком гибок. Во многих случаях может потребоваться специфический способ модификации атрибута. Например, некоторый класс экспортирует счетчик, значения которого нельзя изменять произвольно, а только с шагом +1 или -1:
class COUNTING feature
counter: INTEGER
increment is
-- Увеличение значения счетчика
do
counter := counter + 1
end
decrement is
-- Уменьшение значения счетчика
do
counter := counter - 1
end
end
Аналогичным образом клиенты класса POINT не имеют возможности непосредственно изменять координаты точки x и y. Для этой цели служат экспортированные процедуры translate и scale.
При изучении утверждений мы рассмотрим еще одну принципиальную причину недопустимости непосредственных присваиваний a.attrib := some_value. Причина в том что не любые значения some_value могут быть допустимыми. Можно определить процедуру
set_polygon_size (new_size: INTEGER) is
-- Установить новое значение числа вершин многоугольника
require
new_size >= 3
do
size := new_size
end
параметр которой может равен 3 или больше. Прямое присваивание не позволяет учесть это условие и в результате получается некорректный объект.
Эти рассуждения показывают, что автор класса имеет в своем распоряжении пять возможных уровней предоставления прав доступа клиентов к атрибутам (рис. 7.8).
Рис. 7.8. Возможные варианты прав доступа клиентов к атрибутам
Уровень 0 соответствует полному отсутствию доступа к атрибуту. На уровне 1 открыт доступ только для чтения. На уровне 2 разрешена модификация с помощью специальных алгоритмов. На уровне 3 новое значение может быть присвоено, только если удовлетворяет определенным условиям, как в примере для многоугольника. На уровне 4 ограничения снимаются.
Решение, описанное в данной лекции, следует из приведенного анализа. Экспорт атрибута дает клиентам право доступа только для чтения (уровень 1). Разрешение на модификацию обеспечивается написанием и экспортом соответствующих процедур. Они предоставляют ограниченные права, как в примере для счетчика (уровень 2), право модификации при соблюдении определенных условий (3) и неограниченный доступ (4).
Это решение является развитием идей, существующих в различных ОО-языках:
[x]. В Smalltalk для обеспечения доступа клиентов к атрибуту на уровне 1 приходится писать специальные функции подобные abscissa and ordinate. Это источник дополнительной работы для программиста и причина снижения производительности.
[x]. C++ и Java представляют другую крайность. Если атрибут экспортирован, то он сразу становится доступным на уровне 4 для чтения и для записи путем прямого присваивания в стиле my_point.x := 3.7. Единственный путь реализации других уровней это полное скрытие атрибута и написание экспортированных процедур для поддержки уровней 2 и 4 и функций для уровня 1. Далее все аналогично Smalltalk. Поддержка уровня 3 невозможна в связи с отсутствием в этих языках механизма утверждений.
Данная дискуссия иллюстрирует два важных принципа построения языка: не создавать без необходимости дополнительных проблем программисту и не вводить лишних языковых конструкций.
Оптимизация вызовов
На уровнях 2 и 3 неизбежно использование явных вызовов процедуры подобных my_polygon.set_size (5) для изменения значения атрибута. Существует опасение, что использование такого стиля на уровне 4 негативно скажется на производительности. Тем не менее компилятор может создавать для вызова my_point.set_x (3.7) код столь же эффективный, как и для my_point.x := 3.7, если бы такое присваивание было бы разрешено.
Компилятор ISE добивается этого путем общего механизма непосредственного встраивания кода подпрограмм с подстановкой соответствующих параметров и необходимость вызовов устраняется.Встраивание кода подпрограмм является одним из преобразований, которое должен обеспечивать оптимизирующий компилятор ОО-языка. Модульный стиль разработки, поощряемый объектной технологией, сопряжен с наличием большого числа небольших подпрограмм. Программисты не должны беспокоиться, что соответствующие вызовы приведут к снижению производительности. Они должны заботиться о последовательном соблюдении принципов объектной архитектуры, а не об особенностях выполнения.
В некоторых языках программирования, особенно в Ada и C++, разработчики могут отметить, какие подпрограммы они хотели бы встраивать. По ряду причин предпочтительно, чтобы эта работа выполнялась в режиме автоматической оптимизации.
[x]. Встраивание кода далеко не всегда применимо, и компилятор гораздо корректнее может принять правильное решение.