Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
OOP / books / Osnovi objektno-orientirovannogo programmirovaniya.pdf
Скачиваний:
62
Добавлен:
03.03.2016
Размер:
9.04 Mб
Скачать

Обсуждение

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

Форма объявлений

Отточим наши критические навыки вначале на чем-либо не столь существенном. Поэтому начнем с синтаксиса. Рассмотрим нотацию, используемую при объявлении компонентов. В отличие от многих языков мы не использовали для подпрограмм ключевых слов procedure или function. Форма объявления компонента позволяет отличить, будет ли он атрибутом, процедурой или функцией. Любое объявление компонента всегда начинается с его имени:

f ...

Тем самым сохраняется возможность дальнейшего определения компонента любого типа. Если далее присутствует список параметров

g (a1: A; b1: B; ...) ...

то понятно, что g подпрограмма, которая может быть процедурой или функцией. Далее может следовать:

f: T ...

g (a1: A; b1: B; ...): T ...

В первом примере все еще есть выбор - f может быть либо атрибутом, либо функцией без аргументов. Во втором случае неопределенность заканчивается и g может быть только функцией. Для f неопределенность разрешается в зависимости от того, что следует за T. Если ничего, то f это атрибут, как в следующем примере:

my_file: FILE

Но если далее присутствует ключевое слово is, а за ним тело подпрограммы ( do или варианты once и external, рассматриваемые позже), как в примере:

f: T is -- ...

do ... end

то f - функция. Еще один вариант

f: T is some_value

определяет f к а к атрибут-константу (constant attribute), значение которой равно some_value. (Атрибуты-константы обсуждаются в лекции 18)

Такой синтаксис позволяет легко распознавать различные виды компонентов, подчеркивая

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

Атрибуты или функции?

Рассмотрим подробнее следствия принципа унифицированного доступа и объединения атрибутов и подпрограмм под общим заголовком - компоненты. (См."Унифицированный доступ", лекция 3. См. также данную лекцию.)

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

Рассмотрим класс PERSON, содержащий компонент типа INTEGER без параметров. Если автор клиентского класса записывает выражение

Isabelle.age

то единственно важным будет то, что age возвращает целое число - значение возраста экземпляра PERSON, который во время выполнения присоединен к сущности Isabelle. Компонент age может быть как атрибутом, так и функцией, вычисляющей результат, используя значение атрибута birth_date и текущую дату. Автору клиентского класса нет необходимости знать, какое из этих решений выбрал автор PERSON.

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

Различие в точках зрения поставщика и клиента на атрибут представлено на рис. 7.4 и рис. 7.5, использованных для определения понятия компонента. Рис. 7.5 иллюстрирует разницу между подпрограммами и атрибутами - это внутреннее представление с позиций реализации, используемое поставщиком. Рис. 7.4 в качестве первичного критерия использует разницу между командами и запросами - это внешнее представление клиента.

Решение рассматривать атрибуты и функции без параметров как эквивалентные для клиентов имеет два важных следствия, рассматриваемые подробно в последующих лекциях:

*Первое следствие касается программной документации. Стандартная документация класса для клиента, известная как краткая форма класса, составляется так, чтобы отсутствовала разница в описаниях атрибутов и функций без параметров. (См. "Использование утверждений в документации: краткая форма класса", лекция 11)

*Второе следствие связано с наследованием, как основным способом адаптации программных элементов к новым условиям без разрушения существующего ПО. Если некий класс содержит компонент, представляющий собой функцию без аргументов, то вполне допустимо в классах-потомках переопределить его как атрибут. (См. "Предопределение функции в качестве атрибута", лекция 14)

Экспорт атрибутов

В завершение предшествующей дискуссии необходимо обсудить вопрос об экспорте

атрибутов. Рассмотренный в этой лекции класс POINT имеет атрибуты x и y и экспортирует их клиентам, также как и функции rho и theta. Для получения значения атрибута некоторого объекта используется обычная нотация для вызова компонентов в виде my_point.x или my_point.theta.

Эта возможность экспорта атрибутов отличается от соглашений, принятых во многих ООязыках. Типичным примером является Smalltalk, в котором только подпрограммы (методы) могут быть экспортированы классом, а прямой доступ к атрибутам (свойствам) запрещен.

Следуя подходу 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

Этот подход имеет два недостатка:

*Он побуждает авторов классов писать много маленьких функций, аналогичных abscissa и ordinate. Несмотря на то, что такие функции будут очень короткими, автор класса будет тратить на их написание дополнительные усилия, а их присутствие затрудняет восприятие исходного текста.

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

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

Эта методика удовлетворяет требованиям унифицированного доступа (преимущество для клиентов), упрощает восприятие исходных текстов (преимущество для поставщиков) и повышает эффективность (преимущество для всех).

Доступ клиентов к атрибутам

Экспорт атрибута с использованием рассмотренной техники делает его доступным

клиентам только для чтения в виде 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. Причина в том что не

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