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

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

Сама форма вызова показывает, почему текст подпрограммы ( translate в классе POINT ) не нуждается в дополнительной идентификации объекта Current. Поскольку любой вызов подпрограммы связан с определенной целью, которая явно обозначена при вызове, то при выполнении вызова имя каждого компонента в тексте подпрограммы (например, x в тексте translate ) будет присоединено к той же цели. Таким образом, при выполнении вызова

p1.translate (4.0, -1.5)

каждое вхождение x в тело translate, как в следующей инструкции

x := x + a

означает: " x объекта p1 ".

Из этих соображений следует точный смысл понятия Current, как цели текущего вызова. Так в течение всего времени выполнения приведенного выше вызова Current будет обозначать объект, присоединенный к p1. При другом вызове Current будет обозначать цель нового вызова. Можно сформулировать следующий принцип вызова компонент (Feature Call principle):

Принцип вызова компонента

*(F1) Любой элемент программы может выполняться только как часть вызова подпрограммы.

*(F2) Каждый вызов имеет цель.

Квалифицированные и неквалифицированные вызовы

Выше было отмечено, что ОО-вычисления основаны на вызове компонентов. Как следствие этого положения исходные тексты в действительности содержат гораздо больше вызовов, чем может показаться на первый взгляд. До сих пор рассматривались две формы вызовов:

x.f

x.f (u, v, ...)

Подобные вызовы используют так называемую точечную нотацию и их называют квалифицированными (qualified), так как точно указана цель вызова, идентификатор которой расположен перед точкой.

Однако другие вызовы могут быть неквалифицированны, поскольку их цель не указана. В качестве примера предположим, что необходимо в класс POINT добавить процедуру transform, которая будет комбинацией процедур translate и scale точки. Текст такой процедуры может обращаться к процедурам translate и scale:

transform (a, b, factor: REAL) is

--Сместиться на a по горизонтали, на b по вертикали,

--затем изменить расстояние до начала координат в factor раз.

do

translate (a, b) scale (factor) end

Тело процедуры содержит вызовы translate и scale. В отличие от предыдущих примеров здесь не указана точная цель и не применяется точечная нотация. Такие вызовы называют

неквалифицированными (unqualified).

Неквалифицированные вызовы не нарушают пункта F2 принципа вызова компонент, так как тоже имеют цель. В данном случае целью является текущий экземпляр. Когда процедура transform вызывается по отношению к определенной цели, вызовы translate и scale имеют ту же цель. Фактически приведенный выше код эквивалентен следующему

do

Current.translate (a, b) Current.scale (factor)

Можно переписать любой вызов как квалифицированный, указав Current в качестве цели (строго говоря, это справедливо только для экспортированных компонент). Форма неквалифицированного вызова конечно проще и вполне понятна.

Приведенные неквалифицированные вызовы являются вызовами процедур. Аналогичные соображения можно распространить и на атрибуты, хотя наличие вызовов в этом случае возможно менее очевидно. Ранее было отмечено, что в теле процедуры translate присутствие x в выражении x + a означает поле x текущего экземпляра. Можно истолковать это иначе - как вызов компонента x и выражение в полной форме примет вид Current.x+a.

В общем случае любые инструкции или выражения вида:

f

или:

f (u, v, ...)

фактически являются неквалифицированными вызовами и могут быть переписаны в форме квалифицированных вызовов:

Current.f

Current.f (u, v, ...)

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

Компоненты-операции

Рассмотрение выражения:

x + a

приводит к важному понятию компонента-операции (operator feature). Это понятие может восприниматься как чисто косметическое, имеющее только синтаксическую значимость, и реально не вносящее ничего нового в ОО-метод. Но именно такие синтаксические свойства

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

Для реализации этой идеи нужно догадаться, что выражение x + a содержит не один вызов (компонента x ), а два. В вычислениях, не использующих объектный подход, + рассматривается как операция сложения двух значений x и a типа REAL. Как уже отмечалось, в чистой ООмодели единственным механизмом вычислений является вызов компонентов. Следовательно, можно считать, по крайней мере теоретически, что и сложение является вызовом соответствующего компонента.

Для лучшего понимания необходимо обсудить определение типа REAL. Сформулированное ранее объектное правило (лекция 7) подразумевает, что каждый тип основан на каком-то классе. Это в равной мере относится к предопределенным классам, аналогичным REAL, и к классам, определенным разработчиком, таким как POINT. Предположим, что необходимо описать REAL как класс. Нетрудно определить набор существенных компонентов: арифметические операции (сложение, вычитание, изменение знака...), операции сравнения (меньше чем, больше чем...). Итак, первый набросок будет выглядеть так:

indexing

description: "Действительные числа (не окончательная версия!)" class REAL feature

plus (other: REAL): REAL is

--Сумма текущего значения и other

do

...

end

minus (other: REAL) REAL is

--Разность между текущим значением и other

do

...

end

negated: REAL is

--Текущее значение, взятое с противоположным знаком

do

...

end

less_than (other: REAL): BOOLEAN is

--Текущее значение меньше чем other?

do

...

end

... Другие компоненты ...

end

При использовании такого описания класса уже нельзя более записывать арифметическое выражение в виде: x + a. Вместо этого надо использовать следующий вызов:

x.plus (a)

По аналогии, вместо привычного -x следует теперь писать x.negated.

Можно попытаться оправдать такой отход от привычной математической нотации стремлением к последовательной реализации ОО-модели и призвать в качестве примера Lisp для обоснования возможности отхода от стандартной нотации в сообществе разработчиков ПО.

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

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

x + a

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

indexing

description: "Real numbers" class REAL feature

infix "+" (other: REAL): REAL is

--Сумма текущего значения и other

do

...

end

infix "-" (other: REAL) REAL is

--Разность между текущим значением и other

do

...

end

prefix "-": REAL is

--Текущее значение, взятое с противоположным знаком

do

...

end

infix "<" (other: REAL): BOOLEAN is

--Текущее значение меньше чем other?

do

...

end

... Other features ...

end

Введены два новых ключевых слова - infix и prefix. Единственное синтаксическое новшество заключается в том, что имена компонент не являются идентификаторами (такими как distance или plus ), а записываются в одной из двух форм (В следующей лекции будет показано, как определить "развернутый класс". См. "Роль развернутых типов".)

где заменяется конкретным знаком операции (+, -, *, <, <= и др.). Компонент может иметь имя в инфиксной форме только если является функцией с одним аргументом, примерами могут служить plus, minus и less_than в первоначальной версии класса REAL. Префиксная форма

может использоваться только для функций без аргументов или атрибутов.

Инфиксные и префиксные компоненты, называемые далее компоненты-операции

(operator features), используются аналогично именованным компонентам (identifier features). Существуют лишь два синтаксических различия. Для имен компонентов-операций при их объявлении используются формы infix " " или prefix " ", а не идентификаторы. Вызов компонентов-операций в случае инфиксных компонент имеет вид:

для префиксных:

Компоненты-операции поддерживают только квалифицированные вызовы. Неквалифицированный вызов plus (y) в подпрограмме первой версии класса REAL во второй версии должен быть записан в виде Current + y. Для именованных компонентов аналогичная нотация Current.plus (y) допустима, но обычно не используется.

Кроме указанных отличий во всем остальном компоненты-операции полностью синтаксически эквиваленты именованным компонентам, в частности могут наследоваться обычным образом. Не только базовые классы аналогичные REAL, но и любые другие, могут использовать компоненты-операции, например для функции сложения двух векторов в классе VECTOR вполне допустимо использовать инфиксную компоненту "+".

Операции, используемые в компонентах-операциях, должны подчиняться следующим правилам. Знак операции - последовательность из одного или более отображаемых символов, не содержащая пробелов и переводов строки, причем первым символом может быть только один из ниже перечисленных:

+ - a / < > = \ ^ @ # | &

Ограничения, налагаемые на первый символ, облегчают распознавание инфиксных и префиксных операций.

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

not and or xor and then or else implies

Базовые классы ( INTEGER и другие) используют так называемые стандартные операции:

*префиксные: + - not

*инфиксные: + - a / < > <= >= = // \\ ^ and or xor and then or else implies .

|Здесь // обозначает целочисленное деление, \\ - остаток при целочисленном делении, ^ - операцию возведения в степень, xor - исключающее "или". В классе BOOLEAN and then и or else являются вариантами and и or (отличия обсуждаются далее), implies обозначает импликацию: выражение a implies b эквивалентно ( not a ) or else b . |

Операции, не входящие в число "стандартных", называют свободными операциями. Приведем два примера свободных операций.

*Далее в классе ARRAY будет использован инфиксный компонент-операция "@" для функции, возвращающей указанный элемент массива. Обращение к i -ому элементу массива будет выглядеть как a @ i.

*В класс POINT вместо функции distance можно ввести компонент-операцию " |-| " и расстояние между точками p1 and p2 будет записываться в виде p1 |-| p2, а не как p1.distance(p2).

Все операции имеют фиксированный приоритет, стандартные операции имеют свой обычный приоритет, а все свободные операции обладают более высоким приоритетом.

Использование компонентов-операций позволяет использовать общепринятую нотацию для выражений и одновременно отвечает требованиям полной унификации системы типов. Реализация арифметических и булевых операций как компонентов класса INTEGER вовсе не должна быть причиной снижения производительности. Концептуально a + x является вызовом компонента, но хороший компилятор может создать в результате обработки такого вызова код не менее эффективный, чем компиляторы C, Pascal, Ada или других языков, в которых "+" это жестко зафиксированная языковая конструкция.

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

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