|
su.dbms- SU.DBMS ---------------------------------------------------------------------- From : Victor V. Metelitsa 2:5077/13 29 Jan 2001 15:13:04 To : All Subject : Заметки по поводу Дейта и GemStone -------------------------------------------------------------------------------- Крис Дейт. Введение в системы баз данных. Шестое издание. Диалектика. Киев, Москва 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'а не надо переименовывать "посылку сообщения" в "вызов метода", потому что это разные вещи. Дело в том, что объект может не иметь соответствующего сообщению метода, причем не в результате ошибки, а умышленно (далее осуществляется нечто вроде маршрутизации сообщения). С объединением и проекцией - не вижу никаких проблем. --- GoldED/W32 3.0.1 * Origin: Завод по изготовлению и переработке вакуума: (2:5077/13) Вернуться к списку тем, сортированных по: возрастание даты уменьшение даты тема автор
Архивное /su.dbms/18363a757a82.html, оценка из 5, голосов 10
|