Bags o' Fun.

  Автор: © Jason Steffler
Перевод: © Павел Ступин (Кобальт)


 

Резюме

Те, кто не читал предыдущие статьи этой серии, обязательно сначала ознакомьтесь с разделом "Для чего нужны эти статьи?". В этом месяце мы займемся изучением наследования, полиморфизма, абстрактных классов, а также познакомимся с таким объектом как коллекция. Те, кто хочет прочитать предыдущие статьи серии и быть в курсе новостей, связанных с этими статьями, - рекомендую посетить MST page. Те же, кто не хочет ограничиваться данными статьями и жаждет новых знаний - отсылаю вас на http://mimmow.cc.gatech.edu/377, где можно найти список неплохих ресурсов, которые помогут вам в ваших поисках.

 

Фраза дня

Почему Smalltalk? Да потому что Smalltalk - это по сути упрощенная версия английского. Существительные и глаголы. Объекты - это существительные. Глаголы - это сообщения пересылаемые от одного объекта к другому. Это просто, как 2х2. Нет больше необходимости писать загадочные наборы слов и колдовать над кодом. Это значит, что практически каждый может научиться программировать на Smalltalk.

- Peter William Lount.

 

Наследование

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

Понятие наследования в ООП по смыслу несильно отличается от того, который мы придаем эту слову в повседневной речи. Точно так же, как человек наследует некоторые черты родителей, объекты наследует некоторые свойства родительских объектов. Объекты, помимо всего прочего, наследуют методы и переменные (instance variables) своих родителей. Чтобы на практике ознакомиться с этим понятием, давайте возьмем, к примеру, объект anOrderedCollection класса OrderedCollection. Этот объект достаточно часто используется в Smalltalk. OrderedCollection - это именно то, что указано в названии. А именно - коллекция индексированных объектов. Для лучшего понимания того, как все это можно использовать на практике, давайте обратимся к примерам:

[пример 1]
Проанализируйте данный пример кода. Здесь мы создаем новый объект OrderedCollection, и затем последовательно добавляем туда 1, 2, 3, и в итоге - просим вывести на экран содержимое объекта.

(OrderedCollection new) add: 1; add 2; add 3; yourself.

[пример 2]
Обычно указанный выше код записывается несколько иначе:

(OrderedCollection new)
	add: 1;
	add: 2;
	add: 3;
	yourself.

В этом примере, мы просим класс (помните? класс - это основа для создания новых объектов) OrderedCollection создать новую коллекцию. Затем мы просим новую коллекцию добавить 1 к самой себе. Следующие шаги - последовательное добавление к той же коллекции 2 и 3, и затем - мы просим коллекцию показать свое содержимое. Как правило, нет необходимости использовать команду yourself для обращения к объекту, потому что данное действие выполняется по умолчанию. То есть сам объект является получателем (receiver) сообщения. Но, с другой стороны, сообщение (message) add: возвращает не содержимое коллекции, а значение передаваемых ей параметров. Именно это и происходит в данном случае. Поэтому, если нам нужно вывести содержимое объекта OrderedCollection, создаваемого нами, нам нужно явно указать это, что мы и сделали с помощью yourself. Такой подход может, наверное, вызвать определенные трудности. Теперь посмотрите на следующие кусочки кода, в которых объект создается неявно (имплицитно), в отличие от предыдущего явного|эксплицитного способа. Результат один и тот же:

[пример 3]
В этом примере, мы обращаемся к классу OrderedCollection для создания нового объекта этого класса, который бы содержалв себе значения 1, 2 и 3.

OrderedCollection with: 1 with: 2 with: 3

[пример 4]
Другой способ реализации той же задумки, что и в Примере 3

OrderedCollection withAll: #(1 2 3)

[пример 5]
Теперь, если вы сделаете 'print it' с вышеуказанным кодом, вы увидите объект в виде ASCII: OrderedCollection (1 2 3 ). Если вы при просмотре объекта кликнете на 'self', то увидите:

[пример 6]
Существует несколько различных способов для добавления в коллекцию четвертого элемента типа Integer. Вот один из них, весьма удобный. Предположим у вас есть эта коллекция OrderedCollection, содержащая всего 3 элемента, и вдруг вас осеняет, что на самом деле вам нужно не 3, а 4 элемента. Нет необходимости повторять создание объекта. Просто нужно попросить объект добавить этот элемент как показано на изображении:

[пример 7]
Теперь выделите программный код, введенный в нижней панели, и сделайте с ним 'do it'. Таким образом вы 'просите' объект добавить к себе (self) число 4. Если в это время пункт self был активизирован, то вы заметите, что автоматически произошло обновление (в противном случае, кликните на нем). Вы увидите:

Данный пример является иллюстрацией того, каким колоссальным потенциалом обладает возможность манипулирования объектами и просмотра результатов этих манипуляций в реальном времени. Если в процессе программирования у вас возникают какие-то затруднения, то можно прервать исполнение программы, активизировать проблемный объект и получить предельно точную картину того, что с ним происходит. Если вам необходимо смоделировать определенные условия, можно просто напрямую изменить объект в соответствии с вашими нуждами. Например, к вам в голову вдруг пришла идея о том, что вместо элемента коллекции '4' типа Integer, вам нужен элемент 'four' типа string. Кликните на четвертом элементе, удалите '4', введите новое значение 'four', затем кликнете средней кнопкой мыши чтобы осуществить операцию 'accept'. Теперь значение четвертого элемента - 'four'. Попробуйте щелкнуть на третьем элементе, затем вернитесь к четвертому за подтверждением. Вы увидите:

...И запомните - мы проделали все это без компилирования, линкования и запуска скомпилированной программы. Хорошо, теперь у нас уже есть представление о том, как создавать коллекции. Следующее задание - научиться что-нибудь делать с этими коллекциями, к примеру, добавлять элементы типа Integer. Для этого на сделать 'do it' со следующим кусочком кода:

[пример 8]


	| anOrderedCollection aSum |
	aSum := 0.
	anOrderedCollection := OrderedCollection withAll: #(1 2 3).
	anOrderedCollection do: [:anElement | aSum := aSum + anElement].
	aSum inspect.
Эти строки имеют следующий смысл:
  1. создают временные переменные
  2. инициализируют сумму
  3. создают новую упорядоченную коллекцию, присваивают ее одной из временных переменных
  4. просят упорядоченную коллекцию произвести определенную операцию над каждым элементом, а именно: добавление посредством 'sum' элементов к коллекции
  5. здесь мы просим 'sum' открыть инспектор объектов на определенном элементе (да, это можно делать и программно - круто, не правда ли?)

Те из вас, ребята, кто уже имеет какой-то опыт программирования, наверняка обратят внимание на то, что мы не уделяем никакого внимания на проверку ограничений, то есть на размерность коллекции, и не заботимся об объявлении временных переменных для индексирования коллекции - все это решено уже самой коллекцией по определению. Довольно удобно, к тому же позволяет уменьшить количество совершаемых ошибок. Нам достаточно просто попросить коллекцию сделать что-нибудь с каждым элементом через 'do'.

Итак, возвращаемся непосредственно к наследованию. Как и подсказывает нам имя 'OrderedCollection', данный класс является модификацией класса 'Collection'. 'OrderedCollection' наследует методы и instance-переменные 'Collection'. Если быть более точным, 'OrderedCollection' наследуем их от класса 'SequenceableCollection', который, в свою очередь, наследует непосредственно от 'Collection'. Сегодня в нашем распоряжении UML и целый арсенал другого программного обеспечения для моделирования диаграмм, но в целях экономии времени я ограничусь рабочей моделью, сделанной просто в plain text: я продемонстрирую взаимосвязь между классами, наследование обозначается символом табуляции:

Collection
	SequenceableCollection
		OrderedCollection

Данную взаимосвязь можно понимать и так: 'OrderedCollection' - это разновидность 'SequenceableCollection', а последний класс - разновидность 'Collection'. Например, метод создания объекта, использованный нами: withAll, наследуется от 'Collection'. Смотрите:

Collection
	>>withAll:
	SequenceableCollection
		OrderedCollection

И 'Collection', и 'SequenceableCollection' являются примерами того, что мы называем абстрактными классами, то есть такими, которые, будучи неспособными самостоятельно породить объект, служат логическими отправными точками для конструировани яобъктов. Здесь не имеет значения, что у нас - OrderedCollection, SortedCollection, Bag (неиндексированная коллекция) или что-то еще - важно то, что нам нужно, чтобы все эти классы знали, как реагировать на метод withAll:. Вся прелесть в том, что нам для решения поставленной задачи необходимо внести изменения (обработку withAll:) лишь один раз - все производные классы унаследуют эту способость работы с withAll:.

Если вам необходимо реализовать возможность исключений из общего правила наследования (например, у вас есть класс Heap2, которому нужно работать с withAll: по-особому), тогда вы, в терминах программирования, осуществите overriding метода withAll: класса Heap. Если добавить класс Heap в нашу схему наследования, то получится следующее:

Collection
	>>withAll:
	SequenceableCollection
		OrderedCollection
		Heap
			>>withAll:

Примечание: когда мы обращаемся к методу withAll: классов Heap или OrderedCollection, то одно и то же обращение (message) реализовано дважды и по-разному - это называется полиморфизмом. Еще один пример из серии загадочных терминов, скрывающих за собой достаточно простые вещи.

Из понятия полиморфизма, однако, непосредственно вытекает одно принципиально важное следствие - полиморфизм позволяет перейти от концептуальных рамок процесса принятия решений к новому складу ума: командному. Полиморфизм избавляет разработчика от аспектов программирования, свойственных процедурному программированию - таких как необходимость большого количество кода для проверки различных параметров и условий (например, если это 'OrderedCollection - делай это, если Heap - делай другое, если Bag - делай что-нибудь еще и т.д.). С полиморфизмом же, не имеет никакого значения, с каким типом коллекций вы работаете: при обращении к методу withAll: произойдет именно то, что и должно произойти.

Наконец, если мы добавим в нашу схему еще и SortedCollection и Bag (<вздох облегчения> да, именно отсюда название статьи ;-), то получим:

Collection
	>>withAll:
	Bag
	SequenceableCollection
		OrderedCollection
			SortedCollection
		Heap
			>>withAll:

Данная схема позволяет наглядно убедиться в открывающихся возможностях повторного использования, имеется ввиду возможность сделать|изменить что-то один раз и таким образом дать целому ряду объектов пользоваться этими модификациями. Таким образом достигается синхронизация.

Теперь мы подошли к вопросу о том, каким образом определить где какие объекты находятся и как их использовать. Как и в случае с другими темами в этой серии статей, я буду знакомить вас с одним объектом за раз. Дело в том, что очень распространенной проблемой, с которой сталкиваются начинающие изучать Smalltalk, является то, что у них буквально разбегаются глаза при знакомстве с богатейшей библиотекой классов Smalltalk (количество объектов изчисляется сотнями). Чтобы справиться с этой проблемой, я расширил один из браузеров Smalltalk и создал ScopedBrowser. Это является хорошей иллюстрацией уже упомянутой гибкости: мне удалось изменить работу IDE (интегрированной среды разработки) в соответствии с моим требования. 'ScopedBrowser' позволит нам сконцентрироваться только на тех объектах, которые нам понадобятся в этой статье. Моя задумка заключается в том, чтобы добавоять новые объекты в браузер по мере их изучения. На этот раз, я выбрал все уже упомянутые варианты коллекций плюс еще пару объектов для особо интересующихся (итого: 9 классов). Чтобы открыть этот браузер, сначала нужно добавить код из файла MakingSmalltalk-Article3.st к вашему образу (image) (о том, как это сделать - читайте статью #2 из этой серии). Затем откройте браузер запустив следующий код:

[пример 9] [только для Squeak]
ScopedBrowser openBrowserForArticle3

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

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

Чтобы найти метод withAll:, кликните на кнопке class, затем: Collections-Abstract>Collection>instanceCreationУ этого браузера есть 5 панелей (pane) и 3 кнопки. Ниже предлагается их описание (в порядке слева направо и сверху вниз):

  • панель 1: показывает категории, в данном случае коллекции классов
  • панель 2: показывает классы
  • панель 3: показывает категории - коллекции методов
  • панель 4: показывает методы
  • панель 5: показывает программный код Smalltalk
  • кнопка 1: свертывает браузер таким образом, чтобы показать методы объекта
  • кнопка 2: свертывает браузер таким образом, чтобы показать комментарии к классу
  • кнопка 3: свертывает браузер, чтобы показать методы класса, к которому принадлежит объект

Теперь, если мы вернемся немного назад и кликнем на Collection-Sequenceable>OrderedCollection, то увидим:

Обратите внимание на то, что панель с кодом указывает на родительский по отношению к OrderedCollection класс, наряду с instance-переменными. Если вернуться к абстрактным классам и кликнуть на SequenceableCollection, то мы увидим, что этот класс является наследником класса Collection, о чем мы и говорили. Поэкспериментируйте немного с классами в браузере, чтобы привыкнуть к его интерфэйсу. Найдите классы и методы, которые мы разобрали выше.

В заключении, я бы хотел познакомить вас с еще одной разновидностью браузера - иерархической (hierarchy browser). Такой браузер очень хорош, когда работаешь с иерархиями классов. Чтобы открыть его, сначала снова кликните на OrderCollection, затем - [средней кнопкой] spawn hierarchy. Вы увидите:

[пример 10]

Обратите внимание на отсутствие ограничений иерархического спектра - показывается полная иерархия объектов. Заметьте, что Collection является наследником объекта Object. Логично, не так ли? Вообще говоря, весь Smalltalk таков. Начальной точкой иерархического дерева является объект ProtoObject, который включает в себя самые фундаментальные методы. Возникает естественный вопрос: "А чьим наследником является ProtoObject?" Ответ: ничьим или наследником nil, если быть более точным.

 

В следующей статье

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

 

A Sweet Squeak

В этом разделе вы не найдете разборов кода или примеров. Здесь представляются просто какие-то интересные "штучки" для экспериментов. В этот раз мы займемся скачиванием проектов. Помните, как в первой статье мы создавали проект? Дело в том, что возможности не ограничиваются просто сохранением проекта и его передачей в разные образы. Также можно выкладывать проект в Сеть и скачивать его прямо в ваш образ. Вот пример того, как это можно сделать:

[пример 11] [только для Squeak]

Project thumbnailFromUrl:
'http://www.squeak.org/Squeak2.0/2.7segments/SqueakEasy.extSeg'

Для тех, кто сейчас находится в "режиме" только-чтение - перед вами предстанет проект простой игрушки turtle, после запуска проекта у вас появится возможность управлять черепахой посредством кода.

 

Вопросы и ответы

Этот раздел предназначен для обсуждения вопросов, которые у вас появились после прочтения предыдущих статей (разумеется, надо учитывать и фактор времени (у меня его немного)). Из всех вопросов для ответов я выбираю те, чье представление лучше вписываетс в логику этой серии статей. Если вам нужен более быстрый ответ и ответ вообще (я могу не ответить на ваш вопрос), попробуйте послать вопрос в группу новостей comp.lang.smalltalk, или же в Swiky.

Вопрос: Насколько совместим код, показыванный в примерах с [VisualWorks, VisualAge, Smalltalk/X, Dolphin и т.д.]?

Ответ: Хотя переносимость кода и является моей первостепенной задачей и я даже не пробовал портировать примеры, основной код должен работать|портироваться без проблем. Под основным кодом я понимаю такие вещи как способы использования коллекций, объявления классов, instance-переменных и т.д. Как правило, разные модификации Smalltalk различаются лишь своим GUI. Что касается Squeak, то он обладает многими уникальными свойствами и возможностями, которые невозможно портировать, например: halo, morphic, скачивание проектов.

В этом направлении (имеется ввиду вопрос портирования) я начал кое-что делать в своих статьях: специфический для Squeak код теперь помечается соответствующим образом. Хотя не забывайте, что эти пометки - это всего лишь мое предположение (у меня просто нет времени на дополнительное тестирование и решение одной и той же задачи разными способами). В связи с этим, предлагаю использовать возможности раздела 'talkback' Linux Gazette - если кто-то заметит, что что-то (не)работает на каком-то из диалектов Smalltalk - посылайте в этот раздел соответствующую информацию. Также я решил индексировать примеры, начиная с этой статьи (пример 1, пример 2). Первоначально это не входило в мои планы, так как серия статей задумывалась как нечто неформальное, однако в связи с использованием 'talkback' имеет смысл индексирование - так будет понятней и удобней. Если вам не нравится способ индексация - 'talkback' к вашим услугам.

 

Глоссарий

[абстрактный класс|abstract class]
Класс, который не может использоваться для создания объектов, однако служит хорошей логической базой для создания других классов (конкретных классов). Абстрактные классы выделяются курсивом в иерархиях классов.

[метод класса|class method]
(определение #1 - простое) Метод, являющийся частью реализации класса (не нового объекта!), который, как правило, используется для создания нового объекта. Я обозначаю методы класса так: >>aClassMethod

[конкретный класс|concrete class]
Класс, используемый непосредственно для создания объектов.

[метод образца|instance method]
(определение #1 - простое) Метод, являющийся частью реализации объекта (не класса!). Я обозначаю методы образца так: [полиморфизм|polymorphism] Понятие, характеризующее феномен интерпретирования разными объектами одного и того же сообщения (message) по-разному за счет существования двух разных методов у этих объектов.

[получатель|receiver] Получатель посланного сообщения. Объект, получающий это сообщение. Например, если мы говорим, что aPerson sing, получателем сообщения sing является объект aPerson

[только для Squeak|Squeak-only-suspected] Пометка в программном коде примеров из статей, которая значит, что я предполагаю работоспособность кода только на Squeack. Примечание: не забывайте, что это всего лишь мое предположение - я не собираюсь тратить время на тестирование кода на модификациях Smalltalk или пытаться решать одну и ту же проблему разными способами.

 

Для чего нужны эти статьи?

Когда я написал свою первую статью из серии "Программируем на Smalltalk под Linux" в марте 2000 года, то я ориентировался на аудиторию, состоящую из опытных программистов, которые были плохо знакомы с ООП или Smalltalk. Целью статьи было дать общее представление о моем любимом языке программирования и его использовании под моей любимой операционной системой. С того времени, ко мне поступило большое количество просьб ответить на вопросы касательно программирования на Smalltalk и ООП вообще для начинающих. Тогда я и решил попробовать написать небольшую серию статей такого рода.

Предполагаемая аудитория этой серии - люди, начинающие свое знакомство с миром ООП и программирования вообще. Цель статей - не просто рассказать об ООП, но и показать, каким увлекательным может быть программирование на Smalltalk. Возникает вопрос: "А зачем все это нужно, когда Сеть просто кишит такого рода документацией. Существует две главных причины: 1) Tutorials - это, конечно, просто здорово, но там материал часто представлен в застывшем статическом виде и информация быстро устаревает. 2) Моя серия претендует на то, чтобы быть более захватывающей и удобоваримой, чем Tutorials.

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

 

Почему Smalltalk?

Я убежден в том, что Smalltalk - это лучшая среда для изучения ООП, потому что:

  1. Community Smalltalk-программистов ведет очень бурную деятельность и очень отзывчиво к просьбам о помощи; в отличие от многих других групп новостей - посылая своей вопрос в группу Smalltalk, вы можете смело расчитывать на то, что получите ответ.
  2. Smalltalk прост в изучении. Одна из задумок, преследуемых при создании языка, заключалась в том, чтобы создать обучающую среду для детей.
  3. Smallatalk является на все 100% объектноориентированным языком и только стимулирует развитие навыков ООП (в отличие от языков-миксов, которые вынуждают развивать навыки как процедурного программирования, так и объектного).
  4. Точа свои программистские зубы в Smalltalk, вы растете как ООП программист. Полученные навыки вы сможете применить в любом другом ОО языке (смотрите предыдущий пункт).
  5. Smalltalk является кросс-платформенным: пишите код один раз - запускаете где угодно. Поэтому люди могут учиться в независимости от используемой операционной системе (подход принципиально отличается от исповедуемого M$).
  6. Здесь вы можете просматривать объекты и манипулировать ими в реальном времени. Такой возможности я больше ни встречал нигде.
  7. Smalltalk написан на Smalltalk. Поэтому у вас есть возможность проанализировать то, как устроен язык и, если что-то не нравится, внести коррективы.
  8. Здесь вы освобождены от низкоуровневых проблем. Никакого ручного распределения памяти и явных указателей.
  9. Smalltalk понимается буквально. Я имею ввиду то, что синтаксис предельно прост, и код легко читается.
  10. Существует огромное число Крутых Штук, которые вы сможете провернуть исключительно в Smalltalk (примеры на заставят себя долго ждать - читайте следующие статьи).
  11. ...И самый неотразимый аргумент - программировать на Smalltalk - сплошное удовольствие. В частности, я собираюсь использовать Squeak в качестве своей игрушки. Вы заметите, что это модификация Smalltalk, отличная от тех, что я использовал при изложении материала предыдущих статей. Я никогда не писал на Squeak раньше. Так что я тоже буду учиться по ходу статей. Причины, по которым я выбрал Squeak, следующие:

    • Проект является 100% Open Source
    • В нем есть Воистину Крутые фичи, которых мне не приходилось видеть в других реализациях Smalltalk.
    • Дистрибутив занимает немного место и прост в установке.
    • У проекта есть мощный сайт поддержки - Swiki site (Squeak Wiki)

 

Код Smalltalk

В этот раз без листингов.

 


Copyright © 2000, Jason Steffler.
Copying license http://www.linuxgazette.com/copying.html
Published in Issue 61 of Linux Gazette, January 2001

Вернуться на главную страницу