Содержание
- Классы: обзор, создание, описание;
- Кратчайшее введение в объектную систему Smalltalk;
- Iceberg: Интеграция с Git;
- Обзор System Browser. Переменные-члены.
- Тесты: Написание и запуск;
- Отладка: Чиним упавший тест;
- Что делает Smalltalk особенным:
#dnu
; - Домашнее задание.
Классы: обзор, создание, описание
Кратчайшее введение в объектную систему Smalltalk
О создании классов:
- Всё – объект (просто напоминаю). Классы – тоже объекты.
- Как и в традиционных языках, классы определяют наполнение и поведение своих экземпляров (объектов).
- Поведение объектов определяется методами – их реакциями на сообщения. Методы реализуются классами.
- Классы могут образовывать иерархии наследования. В Смолтоке доступно только одиночное наследование: у класса может быть только один “родитель”. Множественного наследования не предусмотрено.
- Наследование – это сообщение. Классы создаются путем отправки сообщений.
- Если есть сообщение, то есть и объект, которому оно было отправлено. Таким образом, у каждого класса есть свой родительский класс.
Об обработке сообщений:
- Когда объект получает сообщение, происходит процедура, известная как Method lookup: в классе объекта-получателя ищется метод, соответствующий данному сообщению.
- Если в данном классе не найден подходящий метод, поиск осуществляется в родительском классе – и так далее, пока метод не будет найден.
- Если в процессе поиска метода мы спутились до самого нижнего класса в иерархии наследования,
Object
, и там этот метод так же не найден, объект посылает сам себе сообщение#doesNotUnderstand:
с деталями о необработанном сообщении. - Реализация
#doesNotUnderstand:
по-умолчанию определена в классеObject
и выбрасывает соответствующее исключение.
Ответы на вопросы для самых внимательных:
Q: Если у каждого класса есть родитель, то где начинается иерархия классов?
A: Иерархия классов действительно входит в цикл, однако принято считать, что она начинается от
Object
. На самом деле, в Pharo классObject
отнаследован отProtoObject
, который по коду отправляет сообщение наследования сам себе (почти!), а затем выставляет свойsuperclass
вnil
(который, в свою очередь, объект).Q: Если всё – объект, и классы тоже объекты, то объектами каких классов являются сами классы?
A: В Смолтоке создаваемой иерархии классов соответсвует автоматически пополняемая иерархия метаклассов; каждый класс является объектом соответствующего ему метакласса. Иерархия метаклассов является параллельным отражением иерархии классов.
Q: А объектами каких классов являются метаклассы?
A: Каждый метакласс является объектом класса
Metaclass
. В том числе и метакласс самого классаMetaclass
!Q: Что лежит в корне иерархии метаклассов?
A: В корне иерархии метаклассов лежат классы
Behavior
иClass
, которые уже отнаследованы отObject
.
Общая информация о классах и метаклассах дана в книге Pharo By Example, из которой и взята иллюстрация выше. Реализация языков программирования на самих себе является древней забавой; процедура разрыва цикла курицы и яйца называется “бутстраппингом” и для Pharo этот процесс описан, например, здесь.
Iceberg: Интеграция с Git
- Открываем репозиторий для данного занятия и форкаем его в свой аккаунт.
- Открываем рабочий образ Pharo и клонируем свой форк:
- Открываем Iceberg:
Ctrl+O+I
илиBrowse -> Iceberg
; - Жмём
Add
, в списке слева выбираем “Clone from github.com”; - Вводим данные своего форка (название вашего аккаунта, проекта) и меняем протокол на HTTP*.
- Нажимаем
OK
и скрещиваем пальцы.
- Открываем Iceberg:
*
– на момент написания данного руководства (май 2022 г.) наблюдались проблемы в работе Айсберга с новыми ключами SSH, которые с недавних пор требует GitHub. Дело в том, что вы уже могли последовать рекомендациям GitHub и перегенерировать свои ключи SSH на более новые. Однако Pharo для работы с Git использует не консольный клиент git
, который у вас наверняка работает без проблем, а библиотеку libgit
собственной версии, слинкованную с библиотеками ssh
/ssl
тоже конкретных версий – которые, будучи зафиксированными, могут ещё не поддерживать ключи в новом формате. Из-за этого клонирование и пуш в репозитории по протоколу SSH через Айсберг могут быть недоступны. В этом случае pull
/push
можно сделать из консоли, сменив remote
в репозитории, ранее скачанном и зарегистрированном в Айсберге: эти репозитории расположены в директории ~/Pharo/images/$IMAGE_NAME/pharo-local/iceberg
.
- После того, как репозиторий будет загружен, в окне “Repositories” появится соответствующая строчка. Открываем этот репозиторий двойным щелчком и для пакетов
StClassRus
иStClassRus-Tests
нажимаемLoad
в контекстном меню. Пакеты загружены в систему.
Внимание: Позже пакеты были переименованы в StClassRus-Day3
и StClassRus-Day3-Tests
соответственно. В записи видео сохранились старые названия.
Обзор System Browser
(Обзор System Browser, навигация по классам загруженного пакета)
- Где наши пакеты? Как их отфильтровать.
- “Объявление” класса. Как выглядит, из чего состоит.
- Категории методов.
- Методы.
- Class side.
Подробнее о переменных-членах:
- Переменные-члены доступны только в методах класса. По аналогии с С++, считайте, что они всегда
protected
. - Переменные-члены базового класса доступны в методах их дочерних классов.
- Извне получить доступ к внутренним переменным-членам объекта нельзя*, можно только спросить его об этом.
*
на самом деле можно через рефлексию, но в повседневных задачах это не нужно.
Переменные-члены могут быть нескольких типов, определяющих их область видимости и доступности:
Instance variables
– самый частый случай. Собственные переменные объекта. Каждый объект конкретного класса обладает своими независимыми instance variables.Class instance variables
– то же самое, но на стороне класса. Поскольку класс – это объект, он может обладать собственным состоянием и своими внутренними переменными. При наследовании класса и, следовательно, метакласса, в классах наследниках будет доступна своя копия такой переменной. Такие переменные могут быть использованы для реализации синглтонов или каких-либо глобальных счетчиков или пулов.Class variables
– являются разделяемыми (общими) переменными для всех объектов данного класса, и для самого класса, а так же их дочерним классам. Некий аналогstatic
из С++. В отличие от предыдущих двух, такие переменные-члены принято именовать с большой буквы.
Главный хоткей в System browser: Ctrl+S
.
- Обзор класса
STCEmailAddress
. - Работа с классом
STCEmailAddress
. - Разбор overrides.
Тесты: Написание и запуск
Test Runner: Ctrl+O+U
, Browse -> Test Runner
.
- Тесты - это классы;
- Тесты - это Fixture (автоматом);
- Как тесты интегрированы с кодом;
Отладка: Чиним упавший тест
Наша реализация не идеальна, и всегда можно написать такой тест, который с ней “упадёт”.
(Пишем новый тест, попадаем в отладчик, фиксим)
Что делает Smalltalk особенным: #dnu
;
#doesNotUnderstand:
– это сообщение, которое объект отправляет сам себе, когда не знает, как обработать входящее сообщение;- Все правила Method Lookup справедливы и в этом случае;
- Как это использовать: реализуем декоратор.
(Живая демонстрация)
Домашнее задание
Попробуйте реализовать парсер адресов e-mail “по всем правилам”:
- Правила перечислены в RFC3696;
- В Смолтоке доступны комбинаторные парсеры, которые делают решение куда более гибким и элегантным:
Пакет для Pharo устанавливется как:
Metacello new baseline: 'PetitParser'; repository: 'github://moosetechnology/PetitParser:v3.x.x/src'; load.
Во избежание проблем с пуллом и ключами, описанными ранее, рекомендую выставить “Remote type” в “HTTPS” в глобальных настройках Айсберга:
В качестве отправной точки можно использовать такой набросок:
| emailAddress identifier localName hostName zone |
identifier := (#letter asParser / #digit asParser) plus flatten.
localName := identifier. "just reuse parser"
zone := $. asParser, identifier. "reuse parser, again"
hostName := (localName, zone) flatten.
emailAddress := (localName, $@ asParser, hostName)
==> [ :nodes | STCEmailAddress new
user: nodes first;
domain: nodes third;
yourself ].
emailAddress parse: 'user@example.com'
- Задание “со звездочкой”: внедрить такой парсер и валидацию через него в основной класс, покрыть тестами.