Глава 14. Классы поддержки ядра

advertisement
но
14
Классы поддержки ядра
Классы Boolean, True и False
Отношения зависимости между объектами
Обработка сообщений
Ко
нф
ид
ен
ц
Сообщения системных примитивов
иа
Дополнительный протокол класса Object
ль
Класс UndefinedObject
но
Класс UndefinedObject
1
иа
имяКласса does not understand имяСообщения
ль
Объект nil представляет значение любой неинициализированной переменной. Он также
представляет бессмысленный результат. nil — единственный экземпляр класса
UndefinedObject (НеопределенныйОбъект).
Цель включения класса UndefinedObject в систему состоит в обработке сообщений об
ошибках. Типичная ошибка при выполнении выражений языка Smalltalk-80 — посылка
некоторому объекту сообщения, которого он не понимает. Во многих случаях это
происходит оттого, что переменная неправильно инициализирована: имя переменной
вместо того, чтобы ссылаться на некоторый определенный объект, ссылается на nil.
Сообщение об ошибках имеет форму
где имяКласса — класс получателя сообщения, а имяСообщения — имя ошибочно
посланного сообщения.
Обратите внимание, что если бы nil был экземпляром класса Object, то сообщение,
посылаемое ему в ошибочной ситуации, было бы
2
ен
ц
Object does not understand имяСообщения
что менее точно, чем утверждение, что неопределенный объект не понимает сообщение.
Ценой описания класса была достигнута возможность более точной обработки сообщений
об ошибках.
Проверка того, является ли объект равным nil, вводится в классе Object, но
переопределяется в классе UndefinedObject. Класс Object сообщения isNil и notNil
реализует так:
isNil
xfalse
нф
ид
notNil
xtrue
Класс UndefinedObject сообщения isNil и notNil реализует так:
isNil
xtrue
notNil
xfalse
Ко
Поэтому никаких проверок условий в классе Object не требуется.
Классы Boolean, True и False
Протокол для
(Логический);
классами True
протокол, они
1
2
выполнения логических операций обеспечивается классом Boolean
логические значения представляются подклассами класса Boolean —
(Истина) и False (Ложь). Подклассы не добавляют ничего нового в
только переопределяют некоторые сообщения, чтобы добиться более
имяКласса не понимает имяСообщения
Object не понимает имяСообщения
но
эффективного выполнения методов по сравнению с суперклассом. Идея подобна той,
которая реализована при определении сообщений проверок в классах Object и
UndefinedObject; true, единственный экземпляр класса True, знает, что он представляет
логическую истину, а false, единственный экземпляр класса False, знает, что он
представляет логическую ложь. Чтобы проиллюстрировать эту идею, мы показываем
реализацию части протокола управления.
В системе определены следующие логические операции.
Boolean instance protocol
ль
logical operations
& aBoolean
Вычисляющая конъюнкция. Возвращается true, если и получатель
и аргумент aBoolean оба равны true.
| aBoolean
Вычисляющая дизъюнкция. Возвращается true,
получатель, или аргумент aBoolean равны true.
not
Отрицание. Если получатель равен false, возвращает true, иначе
возвращает false.
eqv: aBoolean
Возвращает true, если получатель эквивалентен аргументу
aBoolean; иначе возвращает false.
xor: aBoolean
Исключающее ИЛИ. Возвращает true, если получатель не
эквивалентен aBoolean; иначе возвращает false.
или
иа
если
Boolean instance protocol
controlling
and: alternativeBlock
Невычисляющая конъюнкция. Если получатель — true,
возвращает значение аргумента; иначе возвращает false без
вычисления аргумента.
Невычисляющая дизъюнкция. Если получатель — false,
возвращает значение аргумента; иначе возвращает true без
вычисления аргумента.
нф
ид
or: alternativeBlock
ен
ц
Эти операции конъюнкции и дизъюнкции выполняются так, что их аргумент aBoolean
вычисляется независимо от значения получателя сообщения. Этим они отличаются от
сообщений с именами and: и or:, в которых получатель сообщения определяет, надо ли
вычислять аргумент.
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
Условный оператор. Если получатель — true, возвращает
результат выполнения блока trueAlternativeBlock; иначе
возвращает результат выполнения falseAlternativeBlock.
ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock
Условный оператор. Если получатель — true, возвращает
результат выполнения блока trueAlternativeBlock; иначе
возвращает результат выполнения falseAlternativeBlock.
Условный оператор. Если получатель — true, возвращает
результат выполнения блока trueAlternativeBlock; иначе
возвращает nil.
ifFalse: falseAlternativeBlock
Условный оператор. Если получатель — false, возвращает
результат выполнения блока falseAlternativeBlock; иначе
возвращает nil.
Ко
ifTrue: trueAlternativeBlock
Аргументы сообщений and: и or: должны быть блоками, чтобы их выполнение могло быть
отложено. Условные выражения обеспечиваются с помощью сообщений ifTrue:ifFalse:,
ifFalse:ifTrue:, ifTrue: и ifFalse:, как уже демонстрировалось различными примерами
предыдущих глав. Эти сообщения реализуются в подклассах класса Boolean, поэтому
выполняется только соответствующий аргумент-блок.
В классе True методы такие:
ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock
xtrueAlternativeBlock value
ifTrue: trueAlternativeBlock
xtrueAlternativeBlock value
ль
ifFalse: falseAlternativeBlock
xnil
но
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
xtrueAlternativeBlock value
В классе False методы такие:
ifTrue: trueAlternativeBlock ifFalse: falseAlternativeBlock
xfalseAlternativeBlock value
ifTrue: trueAlternativeBlock
xnil
ен
ц
ifFalse falseAlternativeBlock
xfalseAlternativeBlock value
иа
ifFalse: falseAlternativeBlock ifTrue: trueAlternativeBlock
xfalseAlternativeBlock value
Если x равен 3, то выражение
x > 0 ifTrue: [x
x – 1] ifFalse: [x x + 1]
интерпретируется так: выражение x > 0 возвращает true, метод для ifTrue:ifFalse:
отыскивается в классе True, поэтому без дальнейших проверок части ifFalse: выполняется
блок [x x – 1].
Реализованный таким образом механизм поиска нужного метода, обеспечивает
эффективную реализацию условного управления без привлечения дополнительных
примитивных операций или рекурсивных определений.
нф
ид
Дополнительный протокол класса Object
Протокол класса Object, доступный всем объектам системы, был описан в глава 6.
Некоторые категории сообщений там не обсуждались. Большинство из них принадлежит
той части протокола класса Object, которая обеспечивает поддержку примитивов системы и
отношений зависимости между объектами, а также необходима при обработке сообщений и
примитивных сообщений.
Ко
Отношения зависимости между объектами
Информация в системе Smalltalk-80 представляется объектами. Переменные объектов сами
ссылаются на объекты; в этом смысле объекты явным образом связаны или зависимы друга
от друга. Классы имеют отношения к своим суперклассам и метаклассам; эти классы
совместно используют внешние и внутренние описания и тем самым зависят друга от друга.
Эти две формы зависимости центральные в семантике языка Smalltalk-80. Они
координируют описательную информацию среди объектов.
Классом Object предоставляется дополнительный вид зависимости между объектами.
Его цель — координировать действия между различными объектами. Конкретнее, цель
состоит в том, чтобы дать возможность связать один объект, скажем A, с другим или
но
многими другими объектами, скажем B, так, чтобы B мог быть проинформирован, если
объект A изменится каким-либо образом. Получив информацию об изменении объекта A и о
характере изменений, B может принять решение выполнить некоторые действия, например,
модифицировать собственное состояние. Идея изменения и обновления, следовательно,
ключевой момент этого третьего вида зависимости объектов системы.
Object instance protocol
Добавить аргумент anObject, как один из зависимых от
получателя объектов.
ль
dependents access
addDependent: anObject
Удалить аргумент anObject, как один из зависимых от получателя
объектов.
dependents
Возвращает экземпляр класса OrderedCollection, содержащий все
объекты, зависимые от получателя, то есть все объекты, которые
нужно уведомить, если получатель изменился.
release
Удалить ссылки на объекты, которые могут ссылаться обратно на
получателя. Это сообщение переопределяется всеми подклассами,
которые создают зависимости; выражение super release
включается в любую такую повторную реализацию.
changed: aParameter
update: aParameter
Получатель изменился; изменение обозначается аргументом
aParameter; обычно аргумент — экземпляр класса Symbol,
который является частью протокола изменения зависимого
объекта; по умолчанию сам получатель используется как
аргумент. Информирует все зависимые объекты.
Объект, от которого зависит получатель, изменился. получатель
модифицирует соответствующим образом свое состояние (по
умолчанию ничего не происходит).
Посылает аргумент aSymbol, как унарное сообщение всем
объектам, зависимым от получателя.
нф
ид
broadcast: aSymbol
Получатель изменился некоторым способом; все зависимые от
него объекты информируются посредством посылки каждому из
них сообщения update:.
ен
ц
updating
changed
иа
removeDependent: anObject
broadcast: aSymbol with: anObject
Посылает аргумент aSymbol, как ключевое сообщение с
аргументом anObject всем объектам, зависимым от получателя.
changeRequest
Получатель хочет измениться; проверить все зависимые объекты
и убедиться, что это допустимо.
changeRequestFrom: requestor
Получатель хочет измениться; проверить все зависимые объекты,
исключая requestor и убедиться, что это допустимо.
Ко
В качестве примера рассмотрим объект, который моделирует светофор. Типичный
светофор, расположенный на уличном перекрестке, это объект с тремя фонарями разного
цвета. Только один из этих фонарей может быть включен в каждый данный момент
времени. В этом смысле, состояние “включен-выключен” каждого из трех фонарей зависит
от состояний двух других. Есть несколько способов определить эту зависимость.
Предположим, что мы создаем класс Light (Фонарь) следующим образом.
class name
superclass
instance variable names
class methods
instance creation
setOn
xself new setOn
Light
Object
status
setOff
xself new setOff
но
instance methods
status
true. self changed]
turnOff
self isOn
ifTrue: [status
false]
ль
turnOn
self isOff
ifTrue: [status
testing
isOff
xstatus not
change and update
private
setOn
status
true
setOff
status
false
ен
ц
update: aLight
aLight == self ifFalse: [self turnOff]
иа
isOn
xstatus
Ко
нф
ид
Модель очень проста. Каждый фонарь, экземпляр класса Light, может находится в
состоянии “включен” или “выключен”, поэтому флаг состояния фонаря хранится как
переменная экземпляра с именем status; эта переменная имеет значение true, если фонарь
включен, или значение false, если фонарь выключен. Всякий раз, когда фонарь включается
(turnOn), то он сам себе посылает сообщение changed (изменился). Любое другое
изменение состояния фонаря не приводит к посылке сообщений зависимым объектам, так
как предполагается, что фонарь выключаются в ответ на включение другого фонаря. По
умолчанию в ответ на сообщение changed всем зависимым объектам посылается
сообщение update: self (модифицировать: сам). То есть, объект, состояние которого
изменилось становиться аргументом сообщения update:. В классе Light сообщение update:
реализовано как выключение фонаря. Если аргумент совпадает с получателем, то
сообщение, естественно, игнорируется.
Класс TrafficLight (Светофор) вводится для установки зависимости между
несколькими, входящими в светофор фонарями. Сообщение создания экземпляра with:
создает светофор с числом фонарей, равным аргументу сообщения. Каждый фонарь
светофора зависит от всех остальных. Когда светофор снимается, зависимости между
фонарями исчезают (для уничтожения зависимостей применяется сообщение release,
наследуемое из класса Object; оно переопределяется классом TrafficLight так, чтобы
передать наследуемое сообщение всем фонарям светофора).
class name
superclass
instance variable names
class methods
TrafficLight
Object
lights
instance creation
with: numberOfLights
xself new lights: numberOfLights
но
instance methods
operate
turnOn: lightNumber
(lights at: lightNumber) turnOn
release
super release.
lights do: [:eachLight | eachLight release].
lights nil
иа
private
ль
initialize-release
lights: numberOfLights
lights Array new: (numberOfLights max: 1).
lights at: 1 put: Light setOn.
2 to: numberOfLights do:
[:index | lights at: index put: Light setOff].
lights do:
[:eachLight |
lights do:
[:dependentLight |
eachLight ~~ dependentLight
ifTrue: [eachLight addDependent: dependentLight]]]
ен
ц
нф
ид
Инициализационный метод lights: numberOfLights (фонари: числоФонарей) относится к
частным. Каждый фонарь, кроме первого, создается выключенным. Затем каждый фонарь
связывается со всеми другими фонарями светофора (используется сообщение с именем
addDependent:). Модель светофора функционирует как некий круговой автомат, возможно
временной, последовательно включающий каждый фонарь. Приведенный ниже простой
пример создает экземпляр класса TrafficLight с первым включенным фонарем, а затем
включает каждый из других. Моделирование уличного светофора может включать
различные способы управления работой фонарей.
trafficLight TrafficLight with: 3.
trafficLight turnOn: 2.
trafficLight turnOn: 3
Ко
Сообщение turnOn: aNumber (включить: номер) экземпляру класса TrafficLight вызывает
посылку сообщения turnOn фонарю с номером aNumber. Если фонарь в настоящее время
выключен, то он включается и ему посылается сообщение changed. Сообщение changed
посылает сообщение с именем update: каждому зависимому фонарю светофора; если
зависимый фонарь оказывается включенным, то он выключается.
Особая важность использования введенного протокола зависимости состоит в
поддержке различных графических отображений объекта на экране. Каждый образ объекта
зависим от объекта в том смысле, что, если объект изменился, образ должен быть
проинформирован об этом, чтобы он мог решить, воздействует ли изменение на
отображаемую информацию. Интерфейс пользователя в системе Smalltalk-80 широко
использует эту поддержку для посылки сообщений об изменении объектов; протокол
зависимости также используется для координации действий, выбираемых пользователем из
представленного меню, с содержанием отображаемой на экране информации. Сами меню
тоже могут создаваться путем связывания друг с другом возможных действий, подобно
тому, как это мы делали для фонарей светофора.
Обработка сообщений
ль
но
Вся работа в системе Smalltalk-80 выполняется посредством посылки сообщений объектам.
В целях эффективности, экземпляры класса Message создаются только тогда, когда
происходит ошибка и состояние сообщения должно быть сохранено в некоторой доступной
структуре. Поэтому большинство сообщений в системе не имеют формы непосредственно
создаваемого экземпляра класса Message, который передается затем объекту.
При некоторых обстоятельствах, бывает полезно вычислить имя передаваемого объекту
сообщения. Например, предположим, что объект хранит список возможных имен
сообщений и на основе вычисления выбирает одно из этих имен. Пусть это имя
присваивается в качестве значения переменной selector. Теперь мы хотим передавать
сообщение некоторому объекту, который назовем, допустим, receiver. Для этого мы не
можем просто написать выражение
receiver selector
потому, что такая запись означала бы посылку объекту, на который ссылается receiver,
унарного сообщения selector. Мы можем, однако, записать
иа
receiver perform: selector
Object instance protocol
message handling
perform: aSymbol
ен
ц
Результатом этого будет передача значения аргумента selector как сообщения объекту
receiver. Протокол, поддерживающий эту способность посылать вычисляемое сообщение
объекту, обеспечивается классом Object. Этот протокол включает методы для передачи
объекту как вычисляемых ключевых сообщений, так и вычисляемых унарных сообщений.
Посылает получателю унарное сообщение, указанное аргументом
aSymbol. Аргумент является именем сообщения. Сообщает об
ошибке, если число аргументов, ожидаемых именем сообщения
aSymbol, отлично от нуля.
perform: aSymbol with: anObject
нф
ид
Посылает получателю ключевое сообщение3, определяемое
аргументами. Первый аргумент aSymbol является именем
ключевого сообщения. Второй аргумент anObject является
аргументом ключевого сообщения. Сообщает об ошибке, если
число аргументов, ожидаемых именем aSymbol, не равно 1.
perform: aSymbol with: firstObject with: secondObject
Посылает получателю ключевое сообщение, определяемое
аргументами. Первый аргумент aSymbol является именем
ключевого сообщения. Второй и третий аргументы (firstObject,
secondObject) являются аргументами ключевого сообщения.
Сообщает об ошибке, если число аргументов, ожидаемых именем
aSymbol, не равно 2.
Ко
perform: aSymbol with: firstObject with: secondObject with: thirdObject
Посылает получателю ключевое сообщение, определяемое
аргументами. Первый аргумент aSymbol является именем
ключевого сообщения. Второй, третий и четвертый аргументы
(firstObject, secondObject, thirdObject) являются аргументами
ключевого сообщения. Сообщает об ошибке, если число
аргументов, ожидаемых именем aSymbol, не равно 3.
perform: selector withArguments: anArray
3
Посылает получателю ключевое сообщение, определяемое
аргументами. Первый аргумент selector является именем
ключевого сообщения, второй аргумент anArray содержит все
аргументы ключевого сообщения. Сообщает об ошибке, если
число аргументов, ожидаемых именем selector, не равно размеру
массива anArray.
Не только ключевое, но и бинарное сообщение, что и демонстрируется в приведенном ниже примере
калькулятора. --- Примеч. перев.
instance variable names
class methods
instance creation
new
xsuper new initialize
instance methods
accessing
ен
ц
result
xresult
ль
superclass
иа
Calculator
Object
result
operand
class name
но
Одна из задач, в которой этот протокол может использоваться — расшифровка команд
пользователя. Предположим для примера, что мы хотим создать модель очень простого
калькулятора, в котором операнды предшествуют операциям. Возможная реализация
может представлять калькулятор, имеющий: (1) текущий результат, который является
также первым операндом; (2) возможно неопределенный второй операнд. Каждая операция
— имя сообщения, понятное объекту-результату. Посылка сообщения clear (очистить)
один раз сбрасывает операнд; посылка сообщения clear, когда операнд уже сброшен,
сбрасывает результат.
calculating
apply: operator
(result respondsTo: operator)
ifFalse: [self error: 'operation not understood'].
operand isNil
ifTrue: [result result perform: operator]
ifFalse: [result result perform: operator with: operand]
нф
ид
clear
operand isNil
ifTrue: [result 0]
ifFalse: [operand nil]
operand: aNumber
operand aNumber
private
initialize
result
0
Проиллюстрируем на примере использование класса Calculator (Калькулятор).
hp
Calculator new
Ко
Создали объект hp как калькулятор. Переменные экземпляра result (результат) и operand
(операнд) инициализированы значениями 0 и nil, соответственно.
hp operand: 3
Представим себе, что пользователь нажал клавишу 3 и установил операнд.
hp apply: #+
Пользователь выбрал операцию сложения. Метод для сообщения apply: operator
(выполнить: операция) определяет, что операция понята переменной result и что
переменная operand не nil, поэтому новое значение переменной result устанавливается
выражением
что эквивалентно выражению
0+3
но
result perform: operator with: operand
Метод устанавливает результат равный 3; операнд остается равным 3, поэтому выражение
hp apply: #+
ль
снова добавляет к результату 3, так что теперь он будет равен 6.
hp operand: 1.
hp apply: #-.
hp clear.
hp apply: #squared.
Сообщения системных примитивов
иа
Результат был 6, вычли 1 и возвели в квадрат. Теперь результат стал 25.
Object instance protocol
Меняет указатели объекта получателя и аргумента otherObject.
Все переменные в системе, которые указывали на получателя,
будут теперь указывать на аргумент и наоборот. Сообщает об
ошибке, если хотя бы один из двух этих объектов есть экземпляр
класса SmallInteger.
нф
ид
system primitives
become: otherObject
ен
ц
Имеются ряд сообщений, определенных в классе Object, чья цель состоит в том, чтобы
поддерживать потребности всесторонней реализации системы. Они входят в категорию
системных примитивов. Это сообщения, которые обеспечивают прямой доступ к состоянию
экземпляров и, до некоторой степени, нарушают принцип, согласно которому каждый
объект имеет суверенный контроль над записью значений в свои переменные. Тем не менее,
в таком доступе нуждается интерпретатор языка. Он используется и в обеспечении
описания/реализации класса для среды программирования. Примерами таких сообщений
являются instVarAt: anInteger и instVarAt: anInteger put: anObject, которые,
соответственно, отыскивают и сохраняют значения именованных переменных экземпляра.
instVarAt: index
Возвращает именованную переменную получателя. Нумерация
переменных соответствует порядку, в котором именованные
переменные экземпляра определялись.
instVarAt: index put: value
Сохраняет аргумент value в именованный переменной
получателя. Номера переменных соответствуют порядку, в
котором именованные переменные экземпляра определялись.
Возвращает value.
nextInstance
Возвращает следующий экземпляр после получателя в перечне
всех экземпляров этого класса. Возвращает nil, если все
экземпляры были перечислены.
Ко
asOop
Возвращает целое число, уникальное для получателя.
Вероятно, наиболее необычным и эффективным из всех этих сообщений является
сообщение become: otherObject (становится: другойОбъект). В ответ на это сообщение
должны меняться указатели на получатель и аргумент otherObject. Пример использования
этого сообщения мы находим в реализации сообщения grow в некоторых классах из
иерархии класса Collection. Сообщение grow посылается, когда число элементов,
сохраняемых в наборе фиксированный длины, должно быть увеличено без копирования
набора; копирование нежелательно, потому что нужно сохранить все ссылки от других
объектов на данный набор. Поэтому создают новый набор, в него записывают все элементы
из старого набора, а затем первоначальный набор трансформируют (посредством
grow
| newCollection |
newCollection self species new: self size + self growSize.
newCollection replaceFrom: 1 to: self size with: self.
xself become: newCollection
ль
growSize
x10
но
сообщения become:) в новый. Все указатели на первоначальный набор автоматически
заменяются указателями на новый набор.
Следующий метод для сообщения grow реализован в классе SequenceableCollection.
Ко
нф
ид
ен
ц
иа
Подклассы могут переопределять сообщение growSize, чтобы задать другое число, согласно
которому
следует
расширить
набор.
Download