но 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, чтобы задать другое число, согласно которому следует расширить набор.