- •Оглавление
- •Предисловие
- •Предисловие к русскому изданию
- •Глава 1. Введение
- •1.1. Новые инструменты
- •1.2. Новые приемы
- •1.3. Новый подход
- •Глава 2. Добро пожаловать в Лисп
- •2.1. Форма
- •2.2. Вычисление
- •2.3. Данные
- •2.4. Операции со списками
- •2.5. Истинность
- •2.6. Функции
- •2.7. Рекурсия
- •2.8. Чтение Лиспа
- •2.9. Ввод и вывод
- •2.10. Переменные
- •2.11. Присваивание
- •2.12. Функциональное программирование
- •2.13. Итерация
- •2.14. Функции как объекты
- •2.15. Типы
- •2.16. Заглядывая вперед
- •Итоги главы
- •Упражнения
- •Глава 3. Списки
- •3.1. Ячейки
- •3.2. Равенство
- •3.3. Почему в Лиспе нет указателей
- •3.4. Построение списков
- •3.5. Пример: сжатие
- •3.6. Доступ
- •3.7. Отображающие функции
- •3.8. Деревья
- •3.9. Чтобы понять рекурсию, нужно понять рекурсию
- •3.10. Множества
- •3.11. Последовательности
- •3.12. Стопка
- •3.13. Точечные пары
- •3.14. Ассоциативные списки
- •3.15. Пример: поиск кратчайшего пути
- •3.16. Мусор
- •Итоги главы
- •Упражнения
- •Глава 4. Специализированные структуры данных
- •4.1. Массивы
- •4.2. Пример: бинарный поиск
- •4.3. Строки и знаки
- •4.4. Последовательности
- •4.5. Пример: разбор дат
- •4.6. Структуры
- •4.7. Пример: двоичные деревья поиска
- •4.8. Хеш-таблицы
- •Итоги главы
- •Упражнения
- •Глава 5. Управление
- •5.1. Блоки
- •5.2. Контекст
- •5.3. Условные выражения
- •5.4. Итерации
- •5.5. Множественные значения
- •5.6. Прерывание выполнения
- •5.7. Пример: арифметика над датами
- •Итоги главы
- •Упражнения
- •Глава 6. Функции
- •6.1. Глобальные функции
- •6.2. Локальные функции
- •6.3. Списки параметров
- •6.4. Пример: утилиты
- •6.5. Замыкания
- •6.6. Пример: строители функций
- •6.7. Динамический диапазон
- •6.8. Компиляция
- •6.9. Использование рекурсии
- •Итоги главы
- •Упражнения
- •Глава 7. Ввод и вывод
- •7.1. Потоки
- •7.2. Ввод
- •7.3. Вывод
- •7.4. Пример: замена строк
- •7.5. Макрознаки
- •Итоги главы
- •Упражнения
- •Глава 8. Символы
- •8.1. Имена символов
- •8.2. Списки свойств
- •8.3. А символы-то не маленькие
- •8.4. Создание символов
- •8.5. Использование нескольких пакетов
- •8.6. Ключевые слова
- •8.7. Символы и переменные
- •8.8. Пример: генерация случайного текста
- •Итоги главы
- •Упражнения
- •Глава 9. Числа
- •9.1. Типы
- •9.2. Преобразование и извлечение
- •9.3. Сравнение
- •9.4. Арифметика
- •9.5. Возведение в степень
- •9.6. Тригонометрические функции
- •9.7. Представление
- •9.8. Пример: трассировка лучей
- •Итоги главы
- •Упражнения
- •Глава 10. Макросы
- •10.1. Eval
- •10.2. Макросы
- •10.3. Обратная кавычка
- •10.4. Пример: быстрая сортировка
- •10.5. Проектирование макросов
- •10.6. Обобщенные ссылки
- •10.7. Пример: макросы-утилиты
- •10.8. На Лиспе
- •Итоги главы
- •Упражнения
- •Глава 11. CLOS
- •11.1. Объектно-ориентированное программирование
- •11.2. Классы и экземпляры
- •11.3. Свойства слотов
- •11.4. Суперклассы
- •11.5. Предшествование
- •11.6. Обобщенные функции
- •11.7. Вспомогательные методы
- •11.8. Комбинация методов
- •11.9. Инкапсуляция
- •11.10. Две модели
- •Итоги главы
- •Упражнения
- •Глава 12. Структура
- •12.1. Разделяемая структура
- •12.2. Модификация
- •12.3. Пример: очереди
- •12.4. Деструктивные функции
- •12.5. Пример: двоичные деревья поиска
- •12.6. Пример: двусвязные списки
- •12.7. Циклическая структура
- •12.8. Неизменяемая структура
- •Итоги главы
- •Упражнения
- •Глава 13. Скорость
- •13.1. Правило бутылочного горлышка
- •13.2. Компиляция
- •13.3. Декларации типов
- •13.4. Обходимся без мусора
- •13.5. Пример: заранее выделенные наборы
- •13.6. Быстрые операторы
- •13.7. Две фазы разработки
- •Итоги главы
- •Упражнения
- •Глава 14. Более сложные вопросы
- •14.1. Спецификаторы типов
- •14.2. Бинарные потоки
- •14.3. Макросы чтения
- •14.4. Пакеты
- •14.5. Loop
- •14.6. Особые условия
- •Глава 15. Пример: логический вывод
- •15.1. Цель
- •15.2. Сопоставление
- •15.3. Отвечая на запросы
- •15.4. Анализ
- •Глава 16. Пример: генерация HTML
- •16.1. HTML
- •16.2. Утилиты HTML
- •16.3. Утилита для итерации
- •16.4. Генерация страниц
- •Глава 17. Пример: объекты
- •17.1. Наследование
- •17.2. Множественное наследование
- •17.3. Определение объектов
- •17.4. Функциональный синтаксис
- •17.5. Определение методов
- •17.6. Экземпляры
- •17.7. Новая реализация
- •17.8. Анализ
- •Комментарии
- •Алфавитный указатель
11
CLOS
Объектная система Common Lisp (Common Lisp Object System, CLOS) представляет собой набор операторов для объектно-ориентированного программирования. Всех их объединяет историческое прошлое.° С тех нической точки зрения они ничем не отличаются от остального Com mon Lisp: defmethod – такая же неотъемлемая часть языка, как и defun.
11.1. Объектно-ориентированное программирование
Объектно-ориентированное программирование – это определенное изме нение в организации программ. Это изменение подобно тому, которое произошло в распределении процессорного времени. В 1970 году под мно гопользовательской системой понимался один или два больших мейн фрейма, с которыми соединялось множество простых терминалов. Те перь же под этим принято понимать большой набор рабочих станций, объединенных в сеть. Таким образом, вычислительные мощности те перь распределены между индивидуальными участниками сети, а не централизованы в одном большом компьютере.
Объектно-ориентированное программирование ломает традиционное устройство программ похожим образом. Вместо реализации одной про граммы, управляющей всеми данными, самим этим данным объясня ется, как вести себя, а собственно программа скрывается во взаимодей ствии этих новых «объектов» данных.
Допустим, мы хотим написать программу, вычисляющую площадь раз нообразных двумерных фигур. Одним из подходов могло бы быть напи сание функции, которая выясняет тип объекта и ведет себя соответст вующим образом. Пример такой функции приведен на рис. 11.1.
186 |
Глава 11. CLOS |
(defstruct rectangle height width)
(defstruct circle radius)
(defun area (x)
(cond ((rectangle-p x)
(* (rectangle-height x) (rectangle-width x))) ((circle-p x)
(* pi (expt (circle-radius x) 2)))))
> (let ((r (make-rectangle))) (setf (rectangle-height r) 2
(rectangle-width r) 3) (area r))
6
Рис. 11.1. Представление площадей через структуры и функции
С помощью CLOS мы можем переписать код в другом ключе, как пока зано на рис. 11.2. В объектно-ориентированной модели программа раз бивается на несколько методов, каждый из которых предназначен для соответствующего типа аргумента. Два метода, показанные на рис. 11.2, неявно определяют функцию area, которая будет вести себя так же, как аналогичная функция на рис. 11.1. При вызове area Лисп вызывает опре деленный метод в соответствии с типом аргумента.
Помимо разбиения функций на методы объектно-ориентированное про граммирование предполагает наследование – как для слотов, так и для методов. Пустой список, переданный вторым аргументом в двух вызо вах defclass (рис. 11.2), – это список суперклассов (родительских клас сов). Определим теперь новый класс окрашенных объектов, а затем класс окрашенных кругов, имеющий два суперкласса: circle и colored:
(defclass colored () (color))
(defclass colored-circle (circle colored)
())
Создавая экземпляры класса colored-circle, мы увидим два вида насле дования:
1.Экземпляры colored-circle будут иметь два слота: radius, наследуе мый из класса circle, и color – из класса colored.
2.Так как для класса colored-circle свой метод не определен, вызов area для данного класса будет использовать метод, определенный для класса circle.
11.2. Классы и экземпляры |
187 |
(defclass rectangle () (height width))
(defclass circle () (radius))
(defmethod area ((x rectangle))
(* (slot-value x ’height) (slot-value x ’width)))
(defmethod area ((x circle))
(* pi (expt (slot-value x ’radius) 2)))
> (let ((r (make-instance ’rectangle))) (setf (slot-value r ’height) 2
(slot-value r ’width) 3) (area r))
6
Рис. 11.2. Представление площадей через классы и методы
С практической точки зрения под объектно-ориентированным програм мированием подразумевается способ организации программы в терми нах методов, классов, экземпляров и наследования. Зачем может пона добиться подобная организация? Одно из заявлений объектно-ориенти рованного подхода заключается в том, что он упрощает изменения в программах. Если мы хотим изменить способ отображения объектов класса ob, нам достаточно внести изменения лишь в один метод display этого класса. Если нам нужен новый класс объектов, похожих на ob, но слегка отличающихся от них, мы можем создать подкласс ob, для кото рого зададим необходимые нам свойства, а все остальное унаследуется от родительского класса. А если мы просто хотим получить один объект ob, который ведет себя отлично от остальных, мы можем создать нового потомка ob и напрямую поменять его свойства. Для внесения измене ний в грамотно написанную программу нам необязательно даже смот реть на остальной код.°
11.2.Классы и экземпляры
Вразделе 4.6 при создании структур мы проходили два этапа: с помо щью defstruct создавали определение структуры, затем с помощью спе циальной функции make-point создавали саму структуру. Создание эк земпляров требует двух аналогичных шагов. Для начала определяем класс с помощью defclass:
(defclass circle () (radius center))
Только что мы определили класс circle, который имеет два слота (по добно полям структуры), названные radius и center.
188 |
Глава 11. CLOS |
Чтобы создать экземпляр этого класса, вместо вызова специфичной функции мы воспользуемся общей для всех классов функцией make-in stance, вызванной с именем класса в качестве первого аргумента:
> (setf c (make-instance ’circle)) #<Circle #XC27496>
Доступ к значению слота можно осуществить с помощью slot-value. Но вое значение можно установить с помощью setf:
> (setf (slot-value c ’radius) 1) 1
Как и для структур, значения неинициализированных слотов не опре делены.
11.3. Свойства слотов
Третий аргумент defclass должен содержать список определений слотов. Простейшим определением слота, как в нашем примере, служит символ, представляющий имя слота. В общем случае определение может быть списком, содержащим имя и набор свойств, передаваемых по ключу.
Параметр :accessor неявно создает функцию, обеспечивающую доступ к слоту, убирая необходимость использовать slot-value. Если мы обно вим наше определение класса circle следующим образом:
(defclass circle ()
((radius :accessor circle-radius) (center :accessor circle-center)))
то сможем ссылаться на слоты с помощью функций circle-radius и circ le-center соответственно:
>(setf c (make-instance ’circle)) #<Circle #XC5C726>
>(setf (circle-radius c) 1)
1
> (circle-radius c) 1
Если вместо :accessor использовать параметры :writer или :reader, мож но задать по отдельности либо функцию установки значения слота, ли бо функцию чтения.
Исходное значение слота определяется параметром :initform. Чтобы можно было инициализировать слот в вызове make-instance по ключу, нужно задать имя соответствующего ключа как :initarg этого слота.1 Определение класса, обладающего перечисленными свойствами, будет выглядеть следующим образом:
1В качестве имен используются, как правило, ключевые слова, хотя это не является обязательным требованием.
11.3. Свойства слотов |
189 |
(defclass circle ()
((radius :accessor circle-radius :initarg :radius :initform 1)
(center :accessor circle-center :initarg :center :initform (cons 0 0))))
Теперь вновь созданный экземпляр будет иметь установленные через :initform значения слотов, если с помощью :initarg не заданы другие значения:
>(setf c (make-instance ’circle :radius 3)) #<Circle #XC2DE0E>
>(circle-radius c)
3
> (circle-center c) (0 . 0)
Обратите внимание, что :initarg имеет приоритет перед :initform.
Слоты могут быть разделяемыми, то есть имеющими одинаковое значе ние для всех экземпляров класса. Слот можно сделать разделяемым с помощью декларации :allocation :class. (Другой возможный вариант – :allocation :instance, но, поскольку это значение используется по умол чанию, задавать его нет смысла.) Если изменить значение такого слота в одном экземпляре, это приведет к изменению значения слота и во всех остальных экземплярах данного класса. Поэтому использование разде ляемых слотов обосновано в том случае, когда класс имеет свойство, одинаковое для всех его экземпляров.
К примеру, предположим, что мы хотим смоделировать процесс напол нения содержимым желтой прессы. В нашей модели появление новой темы в одном таблоиде тут же приведет к ее появлению и в остальных, и этот факт нам нужно отразить в нашей программе. Этого можно до биться, если сделать соответствующий теме слот разделяемым. Опреде лим класс tabloid следующим образом:
(defclass tabloid ()
((top-story :accessor tabloid-story :allocation :class)))
Если какая-либо тема попадает на первую страницу одного экземпляра таблоида, она тут же попадает на первую страницу другого:
> (setf daily-blab |
(make-instance |
’tabloid) |
unsolicited-mail (make-instance |
’tabloid)) |
|
#<Tabloid #XC2AB16> |
|
|
>(setf (tabloid-story daily-blab) ’adultery-of-senator) ADULTERY-OF-SENATOR
>(tabloid-story unsolicited-mail)
ADULTERY-OF-SENATOR