Smalltalk в примерах/Сообщения
Сообщения
Как мы упоминали в предыдущей главе, мы посылаем сообщения объектам чтобы они что-либо сделали. В Smalltalk существует три вида сообщений.
Унарные сообщения
правитьУнарное сообщение - это сообщение без аргументов. Например, чтобы сменить знак целого числа мы должны послать унарное сообщение минус.
4 минус
Бинарные сообщения
правитьБинарные. сообщения подобны арифметическим операциям. Они называются бинарными потому что они требуют два объекта.
5 + 3
Здесь мы послали сообщение + с аргументом 3 пятёрке. В отличие от многих языков, где +, -, * и т.д. --- символы операторов связанные с алгебраическими операциями, в Smalltalk они являются сообщениями с одним аргументом. Бинарными являются следующие сообщения
+ - * / ** // \\ < <= > >= = ~= == ~~ & | , @ ->
Многие из них очевидны, но есть и не совсем очевидные. ** возведение в степень
// целочисленное деление, округление до меньшего целого
\\ деление по модулю, возвращает остаток от деления
~= не равно
== идентичность, т.е. один и тот же объект
& логическое и, возвращает истину если оба объекта получатель и аргумент истинны. Получатель и аргумент оба вычисляются, поэтому лучше пользоваться сообщением и:, которое вычисляет только то что необходимо и оно также compiled in-line. Заметьте что оба сообщения & и и:, если получатель истинен, возвращают аргумент, который может и не принадлежать классу Логический.
| логическое или, возвращает истину если получатель или аргументы истинны. Получатель и аргумент оба вычисляются, поэтому лучше пользоваться сообщением или:, которое вычисляет только то что необходимо и оно также compiled in-line. Заметьте что оба сообщения | и или:, если получатель ложен, возвращают аргумент, который может и не принадлежать классу Логический.
, соединение двух совокупностей. Обычно используется для объединения строк.
@ используется для создания экземпляра класса Точка.
-> используется для создания экземпляра класса Association.
Сообщения с ключевым словом
правитьМы всё ещё нуждаемся в другом типе сообщений с одним аргументом и более чем с одним аргументом. Сообщения с ключевым словом являются таковыми. Например:
'elephant' copyFrom: 3 to: 5
возвращает строку 'eph' (В Smalltalk совокупности индексируются начиная с 1, в отличие от C и C++, в которых индексы начинаются с 0; т.е. первый элемент совокупности нумеруется единицей). Двоеточие разделяет слова в сообщении, в котором каждое слово имеет аргумент. (Строго говоря, copyFrom: 3 to: это сообщение и copyFrom:to: это селектор сообщения, но мы также будем называть copyFrom:to: сообщением.)
Последовательные сообщения
правитьМетоды обычно возвращают объект (подробнее об это позже). Это значит что вы можете посылать сообщения последовательно, потому что гарантируется что возвращается объект при посылки каждого сообщения. Например, следующая строка возвращает -3.
3.14 truncated negated
Когда мы посылаем числу с плавающей точкой сообщение усечь, оно возвращает МалоеЦелое, которое в свою очередь возвращает другое МалоеЦелое когда получает сообщение минус. Другим примером может быть строка которая содержит число. Мы хотим сменить знак числа и обратно перевести его в строку. Одним из вариантов этого действия будет:
number := '42' asNumber. negatedNumber := number negated. string := negatedNumber printString.
Однако, из за того что каждый метод возвращает объект мы можем написать данный пример как:
string := ( ( '42' asNumber ) negated ) printString.
Или мы можем опустить скобки пока мы работаем только с унарными сообщениями, все из которых имеют одинаковый приоритет.
string := '42' asNumber negated printString.
Приоритет сообщений
правитьВ отличие от C++, в котором есть очень сложные правила приоритета, в Smalltalk они очень простые:
- Вычисление происходит слева направо.
- Унарные сообщения имеют высший приоритете.
- Бинарные сообщения следующие по приоритету.
- Сообщения с ключевым словом имеют наинизший приоритет.
- Вы можете изменить приоритет используя скобки.
Самое большое отличие от других языков это то, что нет алгебраических операций. + и * это не алгебраические операции --- они просто сообщения. Используя вышеприведённые правила приоритета,
1 + 2 * 3
равно 9, а не 7. Чтобы получить результат, который вы ожидаете, вы должны использовать скобки.
1 + (2 * 3)
Ещё один пример, 2 + '4' asNumber max: 5 даёт 6, потому что в соответствии с правилами, унарное сообщение asNumber даёт
2 + 4 max: 5
Затем бинарное сообщение + даёт 6 max: 5, которое возвращает 6.
Вычисление
30 max: 3 + 4 * 5
дает 35. В этом выражении нет унарных сообщений, а бинарные сообщения выполняются слева направо. После отправления первого сообщения получаем:
30 max: 7 * 5
После следующего бинарного сообщения, мы получаем 30 max: 35, что возвращает 35. Чтобы получить ответ 30, как вы и ожидали, используя обычную алгебру, вы должны использовать скобки, к примеру: 30 max: 3 + (4 * 5).
Что происходит, когда сообщение отправлено
правитьКогда сообщение посылается объекту, Smalltalk проверяет, существует ли метод с таким именем для текущего типа объекта (другими словами, был ли он описан и помещён в класс объекта). Если такой метод есть, он выполняется. Если такого метода нет, этот метод ищется в непосредственном суперклассе. Если это метода нет в суперклассе, он ищется в суперклассе суперкласса. Поиск метода в иерархии суперклассов осуществляется до тех пор, пока не найдется метод с этим именем, который и выполняется. Если достигнут класс Объект и в нём не найден метод, Объект показывает соответствующее сообщение, и дает возможность запустить отладчик и выяснить, что произошло.
Получатель сообщения
правитьВсе сообщения должны посылаться объектам - не существует понятия сообщения самого по себе. Если вы создали объект в методе, очень просто послать ему сообщение. Например:
MyClass>>doThis array := Array new: 3. array at: 1 put: 2.
(Обычно используемое обозначение указания имени метода стороны экземпляра: ClassName>>methodName. Для методов стороны класса: isClassName class>>methodName.)
Self
правитьСредняя длинна метода в Smalltalk 7 строк, поэтому, для того чтобы объект сделал серьёзную работу, хорошей практикой является разделение работы между несколькими методами (подразумевается, что вы стремитесь реализовывать короткие методы). Как вызвать другой метод, определённый в том же объекте? Ответ: объект посылает сообщение самому себе. В Smalltalk есть для этого специальная переменная - self - которая всегда ссылается на объект который вызвал код - получатель сообщения. Заметьте что self ссылается на получателя даже когда код был определён в суперклассе класса получателя.
MyClass>>processObject: anObject self doThisWithObject: anObject. self doThatToObject: anObject.
Если метод нуждается в другом методе для выполнения некоторой работы, пошлите сообщения self. Фактически, если вы не знаете, какому объекту послать сообщение, хорошим правилом будет послать его self.
Super
правитьЕсли вы помните как ищется сообщение, Smalltalk сначала ищет метод в объекте которому пришло сообщение. Если здесь сообщение не найдено, он затем ищет его в суперкласс и т.д. Но что мы должны делать если хотим сразу начать искать сообщение в суперклассе? Smalltalk предоставляет другую специальную переменную, super. Таким образом, если вы хотите начать с суперкласса, пошлите сообщение super.
Когда super должна использоваться? Один из основных примеров это создание экземпляра. Если вы хотите инициализировать переменные экземпляра вы обычно пишете метод initialize method на стороне экземпляра. Вы не можете наследовать метод new пока у вас нет метода initialize, но вы должны написать свой собственный метод new, который будет наследовать поведение метода new от суперкласса. Знак ^, показанный ниже, означает возвращаемое значение.
MyClass>>initialize set some variables MyClass class>>new ^super new initialize
Фактически, super не ссылается на суперкласс объекта которому пришло сообщение. Вместо этого, он ссылается на суперкласс объекта в котором определён выполняемый код. Это тонкое различие, но очень важное, потому что если бы это было не так, было бы легко получить бесконечную рекурсию. Давайте посмотрим почему. Пусть мы имеем иерархию классов ClassTwo подкласс ClassOne, и ClassThree подкласс ClassTwo. Все три класса имеют переменные экземпляра, которые должны быть инициализированы, и код, делающий это, выглядит примерно как следующий.
ClassOne>>initialize set some variables ClassTwo>>initialize super initialize. set some variables ClassThree>>initialize super initialize. set some variables
Когда мы создаём экземпляр ClassThree и выполняем код инициализация ClassTwo из объекта ClassThree, на что ссылается super? Если он ссылается на суперкласс класса выполняющего код, тогда суперклассом должен быть ClassTwo, и сообщение initialize должно быть снова послано ClassTwo. Т.е., мы попали в бесконечный цикл. С другой стороны, если супер ссылается на суперкласс класса, в котором определён код, сообщение должно быть послано ClassOne и бесконечного цикла не возникает.
Ключевым моментом, который надо отметить, является то что self идентифицирует себя самого и может быть просмотрен, так же как переменная, и быть параметром. Однако, super - это не идентификатор и вы не можете просмотреть его и использовать в качестве параметра. Когда вы выполняете метод, компилятор переводит текст в байткод. Когда он встречает слово супер, он создаёт код который заставляет среду выполнения искать метод в суперклассе класса, определившего метод. Поэтому super - это просто механизм для сообщения компилятору как создавать байткод.