Заметки по поводу Дейта и GemStone

1 просмотр
Перейти к первому непрочитанному сообщению

Victor V. Metelitsa

не прочитано,
29 янв. 2001 г., 06:13:0429.01.2001
* Crossposted in RU.SMALLTALK

Крис Дейт. Введение в системы баз данных.
Шестое издание. Диалектика. Киев, Москва 1998.
"Часть VI. Объектно-ориентированные системы."

Очень известная и популярная книжка. К сожалению, в шестой части Дейт допустил
ряд крупных ошибок, которые привели его к неверным выводам. Проблема, наверное,
в том, что Дейт недостаточно хорошо знал Smalltalk и не разобрался в GemStone.

!> Hесколько вступительных слов:

Hачну с того, что Smalltalk можно рассматривать не только как язык
программирования, но и как язык запросов к базе данных. В самом деле, сравним

[1]
SELECT * FROM Employee WHERE lastName = 'Smith'

и
[2]
Employee select: [:eachEmployee | eachEmployee lastName = 'Smith']

Первое выражение (на SQL), уверен, понятно всем читателям этой конференции:
имеется таблица Employee, из нее выбраны все записи, удовлетворяющие условию
lastName = 'Smith'.

Второе (на Smalltalk) означает примерно то же самое:
имеется глобальная переменная по имени Employee, ссылающаяся на коллекцию
объектов. Из нее выбираются такие объекты, для которых верно соответствующее
условие.

Пояснение:
[:eachEmployee | eachEmployee lastName = 'Smith']
:eachEmployee - это параметр.

Перед вертикальной чертой перечисляются параметры. После вертикальной черты
записывается выражение. Все вместе, заключенное в квадратные скобки, называется
"блок кода". В данном примере блоку кода передается параметр, производится
вычисление и в качестве результата появляется true или false.

То, что реально внутри метода #select: запрятан цикл с перебором, никого
волновать не должно. Ведь внутри SQL-ного SELECT тоже запрятан цикл с
перебором. То, что при исполнении SQL-ного SELECT может использоватся индекс,
не имеет отношения к делу. Smalltalk'овый #select: также может анализировать
выражение и использовать индекс (что на практике подтверждает TOPLink). Это все
детали реализации. К делу отношение имеет лишь форма записи. В обоих случаях
речь идет о записи операции на множестве (коллекции) для получения выборки.

Можно привести аналоги более сложным выражениям, и не только выборки, но и
INSERT, UPDATE и DELETE. Благодаря тому, что Smalltalk можно рассматривать как
язык запросов, среды программирования для него можно рассматривать как
интерфейс к объектно-ориентированной базе данных. Прочие языки программирования
не могут это полностью воспроизвести.

GemStone добавляет к обычному Smalltalk'у такие вещи, как журнал транзакций и
crash recovery, многопользовательность и средства разграничения доступа,
индексы и прочие вещи, положенные для СУБД. (Hо тем не менее это чистый
Smalltalk, а не "нечто, на что оказал влияние Smalltalk", и называется он
GemStone Smalltalk, а слово OPAL сейчас практически не упоминается).

!>Далее примечания к тексту...

>Стр. 605. "Обратите внимание, что если для манипулирования данными заданы
>только *заранее* определенные методы, то *незапланированные* запросы
>невозможны, если только для объектов не задана некая специализированная
>структура."

Как же, как же. См. выражение [2]. И передать на сервер строчку с произвольным
выражением (и получить результат) вполне можно.

>Стр. 605. "Методы вызываются с помощью сообщений, которые, по сути,
>являются вызовами функций...".

Для Smalltalk'а это не совсем так. Объекту посылается сообщение, которое
характеризуется селектором и аргументами. Если у объекта есть метод с тем же
селектором, то он и вызывается. В противном случае вызывается метод с
селектором #doesNotUnderstand:. Это не обязательно ошибочная ситуация: на этом
основана куча вещей, включая proxy и анализ скомпилированных блоков. Почему
Дейт называет метод, определяемый им на странице 623, анонимным (про
синтаксические ошибки я вообще стараюсь молчать), не могу понять. Очевидно,
селектор метода - это и есть его имя, в его примере селектор должен быть
#ADD_EMP#:ADD_ENAME:ADD_JOB: (но компилятор такое не проглотит). Дейт пишет про
"сигнатуру", в которую входят имена параметров. Однако на самом деле имена
параметров в селектор _не входят_.

>Стр. 624. "Обратите внимание, что встроенный метод NEW никогда не должен
>использоваться для класса EMPLOYEE [...]".

Потому что пример неправильный. Правильно было создать в классе Employee метод
#initialize, который добавляет self в ESet (Set of Employee), затем, возможно
(нужное могло быть уже сделано в суперклассе), переопределить метод #new так:

Employee class>>#new

^super new initialize

Можно подумать также, что Дейт не знал о возможности перекрытия методов (или
знал, но не осознавал).

>Стр. 626. "Обратите внимание, что (в соответствии с используемым
>представлением иерархии контейнеров) здесь не было создано 'множество
>*всех* дисциплин'".

Во всяком случае, никто не заставлял использовать "иерархию контейнеров" и
никто не мешал сделать "множество всех дисциплин".

[Примечание:

В отличие от реляционных СУБД, где каждая запись физически присутствует в одной
и только одной таблице, GemStone всегда имеет дело со ссылками (исключением
могут быть лишь экземпляры SmallInteger, Characher и nil), и ссылки на один и
тот же объект могут содержать разные коллекции (и не только коллекции).

Правда, это несколько затрудняет удаление, поскольку формально никакого
удаления-то и нет. Просто, если на объект никто не ссылается, его в конце
концов съест сборщик мусора. Итак, чтобы удалить объект, надо удалить его из
всех коллекций (необходимое, но недостаточное условие). Удобно сделать так,
чтобы объект знал, кто именно на него ссылается, и сам убрал все ссылки на себя
"перед смертью". Звучит, может быть, и сложно, но делается просто (см. также
ниже).

Казалось бы, Дейт об этом в курсе, но почему же тогда он не сделал "множество
всех дисциплин", а потом жалуется на неудобства?
]


>Стр. 627. "Прежде чем приступать к подробному описанию операций
>извлечения, следует отметить, что (хотя это и вполне очевидно) язык OPAL,
>как и другие объектно-ориентированные языки в целом, функционирует по
>принципу последовательной обработки записей, а не множеств.
>Следовательно, для решения большинства проблем программист должен создать
>некоторую процедуру.".

Утверждения неверны. См. самое начало. Метод можно и создать, почему бы и нет,
но здесь его следует рассматривать скорее как аналог VIEW.

> Стр. 628. "Квадратные скобки ... можно заменить круглыми".
а самом деле фигурными.

>Стр. 629. "В языке OPAL циклы поддерживаются с помощью специального
>метода DO".

Как обычно, поправки:
* Язык - Smalltalk, а не OPAL;
* Метод - #do:, не DO, и никакой не "специальный" (нет такого понятия -
"специальный метод", а есть просто методы);
* Поддерживаются не циклы, а операции над множествами (точнее, коллекциями).

Пример:

коллекция do: [:параметр | выражение ].

Сравним

[3]
UPDATE Employee SET lastName = 'Smith'

[4]
Employee do: [:eachEmployee | eachEmployee lastName: 'Smith']


Это означает примерно то же самое. Hам нет никакого дела до реализации,
а записано одно и то же.

С условием фильтрации Smalltalk начинает выглядеть громоздким:


[5]
UPDATE Employee SET lastName = 'Smith'
WHERE lastName = 'Simpson'

[6]
(Employee select: [:eachEmployee | eachEmployee lastName = 'Simpson'])
do: [:eachEmployee | eachEmployee lastName: 'Smith']

однако есть и возможность разного рода оптимизаций.

> Стр. 630.

Как выполнить операцию DELETE CASCADE?

ОТВЕТ:
Каждый объект должен знать ссылающихся на него и уметь удалить ссылки на себя.
Hазовем соответствующий метод #removeMe.

Далее, пустить мы удаляем некий объект x, который ссылается на объекты класса Y
в отношении 1:N, т.е. имеет переменную экземпляра по имени ys с коллекцией
объектов класса Y. Тогда метод removeMe объекта x должен содержать

(ys copy) do: [:each | each removeMe].

Вот и все! А если приложить немного мозгов, то можно будет устроить так: метод
#removeMe будет един, и в каждом классе, где требуется каскадное удаление,
потребуется не переписывать #removeMe, а просто указать коллекцию имен
переменных в некотором методе (назовем его #selectorsForCascadeDelete), и
выражение будет выглядеть как

self selectorsForCascadeDelete do:
[:eachSelector | ((self perform: eachSelector) copy) do:
[:each | each removeMe]].

[Примечание: Для чего нужна посылка сообщения #copy? Обычно не рекомендуется
добавлять/удалять элементы в той коллекции, где производится #do:. Так #copy
создаст временную коллекцию, к которой и будет применен #do:, затем она
исчезнет].

> Стр. 630. "Обычно для этого нужно... а также объектный класс
> 'семейства', например ESET, на основе которого можно собирать отдельные
> экземпляры...'.

Вовсе нет. Класс ESET не нужен, можно обойтись Set'ом. Тут, кстати, можно еще
вспомнить определения класса EMPLOYEE, которое я перепишу так, чтобы оно было
больше похоже на настоящее:

Object
subclass: 'Employee'
instvarNames: #('empNo' 'eName' 'job')
constraints: #[ #['empNo', String],
#['eName', String],
#['job', String]
].

Дейт не указал, что ограничения не обязательны. Определение могло выглядеть
и так:

Object
subclass: 'Employee'
instvarNames: #('empNo' 'eName' 'job')
constraints: #[].

что было бы эквивалентом

Object
subclass: 'Employee'
instvarNames: #('empNo' 'eName' 'job')
constraints: #[ #['empNo', Object],
#['eName', Object],
#['job', Object]
].

и в таком случае в переменные можно присваивать значения (или ссылки на них)
какого угодно класса. То же самое относится и к коллекциям (ESET и т.п.).

Далее. Пусть в ограничениях указано #['varName', SomeClass], тогда в varName
может быть не только (ссылка/значение) класса SomeClass, но и любого полкласса
SomeClass.

В подклассах можно усиливать ограничения, например

Employee
subclass: 'SubEmployee'
instvarNames: #()
constraints: #[ #['empNo', SubclassOfString]
].

> Стр. 631. "Дополнительные замечания".

(Как знает и Дейт) обычно объект в ОО-системе хранит в себе ссылки на то, к
чему он имеет отношение. Если объекты класса A находятся в отношении 1:N к
объектам класса B, то обычно объект класса A содержит коллекцию объектам класса
B и одновременно объект класса B - ссылку на объект класса A, причем это взаимо
согласованно (Пример: учитель 'Александров' содержит коллекцию ссылок на
учеников {'Иванов', 'Петров', 'Сидоров'} и одновременно 'Иванов', 'Петров',
'Сидоров' имеют по ссылке на учителя по фамилии 'Александров'). Одновременно
можно иметь также коллекцию всех учителей, всех учеников и т.д.

Если данные организованы именно так, то проблем с запросами не будет. Считаю,
что заявление Дейта "В *объектно-ориентированных* системах, наоборот,
симметричная эксплуатация не предусмотрена" - неверно.

А если возникают вопросы типа "методом какого класса это должно быть - X или
Y?", обычный ответ - "реализуй и там, и там". Сперва там, где это будет
наиболее эффективно, затем из другого вызывается этот.

> Стр. 631. Оператор соединения (JOIN).
Вопрос решается по разному в разных ситуациях. Здесь подробности приводить не
буду.

>Стр. 635. "В объектно-ориентированных системах обычно используется два
>языка: один для программирования приложений, а другой для создания
>запросов".

Hо не в GemStone. Smalltalk'а хватает для всего.

>Стр. 642. "В объектно-ориентированной системе, наоборот, это ограничение
>почти определенно будет приведено в действие с помощью некоторой
>*процедуры*".

Точнее, программистом будет исправлен код соответствующего set-метода
(методов).

Впрочем, если быть предусмотрительным, то код set-метода можно не исправлять.
Решение: Пусть объект должен знать коллекцию имен неких check-методов. Перед
присваиванием set-метод должен вызвать каждый метод из той коллекции и, в
случае непрохождения проверки, выбросить исключение (или check-метод сам
выбросит исключение). Коллекция может быть общей для класса или индивидуальной
для каждого объекта.

Таким образом, добавление новой проверки сводится к

1) Добавлению метода
2) Добавлению его имени в соответствующую коллекцию.

и (после некоторого дополнительного размышления) отпадают вопросы со стр. 642.

>Стр. 643. "Выше уже упоминалось, что в объектно-ориентированных системах
>существуют определенные трудности с обработкой *незапланированных*
>запросов (также называемых запросами типа *ad hic)".

Сколько угодно упоминайте неправду, правдой от этого она не станет. GemStone не
имеет таких трудностей. Как на SQL-сервер можно отправить строчку с
SQL-выражением, так и на GemStone можно отправить строчку со
Smalltalk-выражением и получить какой-то ответ (например, результирующую
коллекцию).

> Стр.645. "Каталог. Где и в каком виде находится каталог в
> объектно-ориентированной системе? Существуют ли какие-либо стандарты в
> отношении каталогов?"

В клиентском Smalltalk'е каталог один (по имени Smalltalk), а GemStone их может
иметь неограниченное количество (один, понятно, самый главный, другие могут
быть совместно используемыми или же частными, что позволяет иметь в системе
разные одноименные классы и таким образом разработчики не навредят друг другу).
Для пользователей это прозрачно.

Поэтому то, что Дейт пишет ниже про отличие реляционной системы от ОО-системы,
неверно. GemStone как минимум такая же "готовая для употребления" система, как
и реляционка.

О стандарте говорить не приходится, поскольку GemStone единственный в своем
роде.

> 645. "Является ли значение nil членом каждого класса?"

nil isKindOf: Object
Ответ: true
nil isMemberOf: Object
Ответ: false


25-ю главу даже можно не читать.

> Стр.652.

Для Smalltalk'а не надо переименовывать "посылку сообщения" в "вызов метода",
потому что это разные вещи. Дело в том, что объект может не иметь
соответствующего сообщению метода, причем не в результате ошибки, а умышленно
(далее осуществляется нечто вроде маршрутизации сообщения).

С объединением и проекцией - не вижу никаких проблем.

Ответить всем
Написать сообщение автору
Переслать
0 новых сообщений