учебники, программирование, основы, введение в,

 

Сохранение объектов и базы данных (БД)

Сохраняемость средствами языка
Для удовлетворения многих потребностей в сохраняемости достаточно иметь связанный с окружением разработки набор механизмов для записи объектов в файлах и их чтения. Для простых объектов, таких как числа или символы, можно использовать средства ввода-вывода, аналогичные средствам традиционного программирования.
Сохранение и извлечение структур объектов
Как только появляются составные объекты, простое запоминание и извлечение индивидуальных объектов становится недостаточным, поскольку в них могут находиться ссылки на другие объекты, а объект, лишенный своих связников (см.курса "Основы объектно-ориентированного программирования"), некорректен. Это наблюдение привело нас вкурса "Основы объектно-ориентированного программирования" к принципу Замыкания Сохраняемости, утверждающему, что всякий механизм сохранения и возвращения должен обрабатывать вместе с некоторым объектом всех его прямых и непрямых связников, что показано на.
Принцип Замыкания Сохраняемости утверждает, что всякий механизм, сохраняющий O1, должен также сохранить все объекты, на которые он непосредственно или опосредованно ссылается, иначе при извлечении этой структуры можно получить бессмысленное значение ("висящую ссылку") в поле loved_one объекта O1.
Мы рассмотрели механизмы класса STORABLE, предоставляющие соответствующие средства: store для сохранения структуры объекта и retrieved для ее извлечения. Это ценный механизм, чье присутствие в ОО-окружении само по себе является большим преимуществом перед традиционными окружениями. Вкурса "Основы объектно-ориентированного программирования" приведен типичный пример его использования: реализация команды редактора SAVE. Вот еще один пример из практики нашей фирмы ISE. Наш компилятор выполняет несколько проходов по представлениям текста программы. Первый проход создает внутреннее представление, называемое Деревом Абстрактного Синтаксиса (Abstract Syntax Tree (AST)). Задача последующих проходов заключается в постепенном добавлении семантической информации в AST ("украшении дерева") до тех пор, пока ее не станет достаточно для генерации целевого кода компилятором. Каждый проход завершается операцией store; а следующий проход начинается восстановлением AST с помощью операции retrieved.
Механизм STORABLE работает не только на файлах, но и на сетевых соединениях таких, как сокеты; он на самом деле лежит в основе библиотеки клиент-сервер Net.
Форматы сохранения
У процедуры store имеется несколько вариантов. Один, basic_store (базовое_сохранение), сохраняет объекты для их последующего возвращения в ту же систему, работающую на машине той же архитектуры, в процессе того же или последующего ее исполнения. Эти предположения позволяют использовать наиболее компактную форму представления объектов.
Другой вариант, independent_store (независимое_сохранение), обходится без этих предположений; представление объекта в нем не зависит от платформы и от системы. Поэтому оно занимает несколько больше места, так как использует переносимое представление для чисел с плавающей точкой и для других числовых значений, а также должно включать некоторую простую информацию о классах системы. Но он важен для систем типа клиент-сервер, которые должны обмениваться потенциально большими и сложными наборами объектов, находящимися на машинах весьма разных архитектур, работающих в различных системах. Например, сервер на рабочей станции и клиент на PC могут выполнять два разных приложения и взаимодействовать с помощью библиотеки Net. Сервер приложения выполняет основные вычисления, а приложение клиента реализует интерфейс пользователя, используя графическую библиотеку, например Vision.
Заметим, что только сохранение требует нескольких процедур - basic_store, independent_store. Хотя реализация операции возврата для каждого формата своя, всегда можно использовать один компонент retrieved, реализация которого определит формат возвращаемых данных, используемый в файле или сети, и автоматически применит соответствующий алгоритм извлечения.
Вне рамок замыкания сохраняемости
Принцип Замыкания Сохраняемости теоретически применим ко всем видам сохранения. Как мы видели, это позволяет сохранить совместность сохраненных и восстановленных объектов.
Но в некоторых практических ситуациях требуется немного изменить структуру данных перед тем, как к ней будут применены такие механизмы, как STORABLE или средства ОО-баз данных, рассматриваемые далее в этой лекции. Иначе можно получить больше, чем хотелось бы.
Такая проблема возникает, в частности, из-за разделяемых структур, как в следующем примере.
Требуется заархивировать сравнительно небольшую структуру данных. Так как она содержит одну или более ссылок на большую разделяемую структуру, то принцип замыкания сохраняемости требует архивирования и этой структуры. В ряде случаев этого делать не хочется. Например, как показано на, объект личность может через поле address ссылаться на гораздо большее множество объектов, представляющих географическую информацию. Аналогичная ситуация возникает в продукте ArchiText фирмы ISE, позволяющем пользователям манипулировать структурами таких документов, как программы или спецификации. Каждый документ, подобно структуре FAMILY на, содержит ссылку на структуру, представляющую основную грамматику, играющую ту же роль, что и структура CITY для FAMILY. Мы хотели бы сохранять документ, а не грамматику, которая уже где-то имеется и которую разделяют многие документы.
В таких случаях хочется "оборвать" ссылки на разделяемые структуры перед сохранением ссылающейся структуры. Однако это тонкая процедура. Во-первых, нужно, как всегда, быть уверенным, что в момент новой загрузки объекты останутся совместными - будут удовлетворять своим инвариантам. Но здесь есть и практическая проблема: как обойтись без усложнений и ошибок? На самом деле, не хотелось бы менять исходную структуру, ссылки нужно оборвать только в сохраняемой версии.
И вновь методы построения ОО-ПО дают элегантное решение проблемы, основанное на идеях классов поведения, рассмотренных при обсуждении наследования. Одна из версий процедуры сохранения custom_independent_store работала так же, как и предопределенная процедура independent_store. Но она позволяла также каждому потомку библиотечного класса ACTIONABLE переопределять ряд процедур, которые по умолчанию ничего не делали, например, процедуры pre_store и post_store, выполняющиеся непосредственно перед и после сохранения объекта. Таким образом, можно, чтобы pre_store выполняла:
preserve; address := Void,
где preserve - это тоже компонент ACTIONABLE, который куда-нибудь безопасно копирует объект. Тогда post_action будет выполнять вызов:
restore,
восстанавливающий объект из сохраненной копии.
В общем случае того же эффекта можно добиться с помощью вызова вида:
store_ignore ("address"),
где store_ignore получает в качестве аргумента имя поля. Так как реализация store_ignore может просто пропускать поле, устраняя необходимость двустороннего копирования посредством preserve и restore, то в данном случае это будет более эффективно, но механизм pre_store-post_store является общим, позволяя выполнять необходимые действия до и после сохранения. Разумеется, нужно убедиться в том, что эти действия не будут неблагоприятно влиять на объекты.
Аналогично можно справиться и с проблемой несовместности во время возвращения, для этого достаточно переопределить процедуру post_retrieve, выполняемую непосредственно перед тем, как восстанавливаемый объект присоединится к сообществу уже одобренных объектов. Например, приложение может переопределить post_retrieve в нужном классе-наследнике ACTIONABLE, чтобы она работала так:
address := my_city_structure.address_value (...)
тем самым снова делая объект представительным, еще до того, как он сможет нарушить инвариант своего класса или какое-нибудь неформальное ограничение.
Конечно, нужно соблюдать некоторые правила, связанные с механизмом класса ACTIONABLE; в частности, pre_store не должна вносить в структуры данных никаких изменений, которые не были бы сразу же исправлены процедурой post_store. Нужно также обеспечить, чтобы post_retrieve выполняла необходимые действия (часто те же, что и post_store) для корректировки всех несовместностей, внесенных в сохраненные данные процедурой pre_store. Предложенный механизм, используемый с соблюдением указанных правил, позволит вам остаться верным духу принципа Замыкания Сохраняемости, делая его применение более гибким.
Эволюция схемы
Этот общий вопрос возникает при всех подходах к ОО-сохраняемости. Классы могут меняться. Что произойдет, если изменится класс, экземпляры которого находятся где-то на постоянном хранении? Этот вопрос называют проблемой эволюции схемы.


Слово схема (schema) пришло из мира реляционных баз данных, где она задает архитектуру БД: множество ее отношений с указанием того, что называется их типами, - число полей и тип каждого поля. В ОО-контексте схема тоже будет множеством типов, определяемых в этом случае классами.

Хотя некоторые средства разработки и системы баз данных обладают интересными средствами для эволюции ОО-схем, ни одно из них не дает полностью удовлетворительного решения. Давайте, определим компоненты полного подхода.
Будет полезно ввести некоторую точную терминологию. Эволюция схемы имеет место, если хотя бы один класс системы, возвращающей объекты (возвращающая система), отличается от своего прототипа в системе, сохранившей эти объекты (сохраняющая система). Рассогласование при возврате объекта или просто рассогласование объекта имеет место, когда возвращающая система реально возвращает некоторый объект, у которого изменился породивший его класс. Рассогласование объекта - это следствие эволюции схемы одного или нескольких классов, отражающееся на конкретном объекте.


Напомним, несмотря на термины "сохраняющая система" и "возвращающая система" наше обсуждение применимо не только к сохранению и возврату, использующим файлы и БД, но также и к передаче объектов по сети, как в библиотеке Net. В этом случае более аккуратными терминами были бы "посылающая система" и "получающая система".

Для упрощения обсуждения примем обычное предположение, что программная система не изменяется в процессе ее выполнения. Это означает, что все сохраненные экземпляры класса относятся к одной и той же версии, поэтому во время возвращения либо все они приведут к рассогласованию, либо ни один из них не будет рассогласован. Это предположение не слишком ограничительное, оно не исключает случая баз данных, которые содержат экземпляры многих разных версий одного класса, созданные различными выполнениями системы.
Наивные подходы
Мы можем исключить два крайних подхода к эволюции схем:

  • Отказ от ранее сохраненных объектов (революция схемы!). Разработчиков нового приложения эта идея может привлечь, так как облегчит им жизнь. Но пользователи этого приложения вряд ли будут в восторге.
  • Переход к новому формату, требующий единовременного преобразования всех старых объектов. Хотя это решение может в ряде случаев подойти, оно не годится для большого хранилища объектов или для хранилища, которое должно быть постоянно доступно.

На самом деле нам нужен способ трансформации объектов "на лету" в то время, когда они возвращаются или изменяются. Такое решение является наиболее общим, и далее мы будем рассматривать только его.


Если потребуется механизм одновременной трансформации многих объектов, то механизм "на лету" легко позволит это сделать: достаточно написать маленькую систему, которая возвращает все существующие объекты, используя новые классы, при необходимости применяет трансформацию на лету и все сохраняет.

Преобразование объектов на лету
Механика преобразования на лету может оказаться весьма мудреной: мы должны быть очень внимательны, чтобы не получить в результате испорченные объекты или БД.
Во-первых, у приложения может не оказаться права изменять запомненный объект из-за существования разных версий породившего его класса. Это вполне разумно, поскольку другие приложения могут все еще использовать старую версию этого объекта. Проблема эта не нова для баз данных. Можно сделать так, чтобы используемый приложением объект был совместим с описанием собственного класса; механизм преобразования на лету обеспечит выполнение этого свойства. Заносить ли преобразованный объект обратно - это отдельный вопрос, классический вопрос привилегированного доступа, возникающий всякий раз, когда несколько приложений или несколько сессий одного и того же приложения получают доступ к сохранению данных. Различные его решения предлагаются базами данных, обычными и ОО.
Независимо от ответа на вопрос о сохранении после изменения более новая и трудная проблема состоит в том, что каждое приложение должно делать с устаревшими объектами. Эволюция схемы включает три отдельных аспекта - выявление, извещение и исправление:

  • выявление (Detection) - обнаружение рассогласований объекта (восстанавливаемый объект устарел);
  • извещение (Notification) - уведомление системы о рассогласовании объекта, чтобы она смогла соответствующим образом на это прореагировать, а не продолжала работать с неправильным объектом (вероятная причина главной неприятности в будущем!);
  • исправление (Correction) - приведение рассогласованного объекта в согласованное состояние, т. е. по превращению его в корректный экземпляр новой версии своего класса - гражданина или по крайней мере постоянного резидента системы.

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

  • При использовании системы управления конфигурацией можно регистрировать каждую новую версию класса и получать в ответ имя этой версии (или самому задавать это имя).
  • Возможна и автоматическая схема, аналогичная возможности автоматической идентификации в OLE 2 фирмы Майкрософт или методам, используемым для присвоения "динамических IP-адресов" компьютерам в Интернете. Эти методы основаны на присвоении случайных номеров, достаточно больших для того, чтобы сделать вероятность совпадения бесконечно малой.

Для каждого из этих решений требуется некоторый централизованный регистр. Если вы хотите избежать связанных с этим трудностей, то используйте структурный подход. Его идея в том, что каждая версия класса имеет свой дескриптор, строящийся по текущей структуре, заданной в объявлении класса. Механизм сохранения объектов должен сохранять дескрипторы их классов. (Конечно, при сохранении многих экземпляров одного класса достаточно запомнить только одну копию его дескриптора.) После этого механизм выявления рассогласований прост: достаточно сравнить дескриптор класса каждого сохраненного объекта с новым дескриптором этого класса. Если они различны, то имеется рассогласованный объект.
Что входит в дескриптор класса? Ответ связан с соотношением между эффективностью и надежностью. Из соображений эффективности не хотелось бы тратить много места на информацию о классе или чересчур много времени на сравнение дескрипторов во время возвращения, но надежность требует минимизации риска пропуска рассогласования. Вот несколько возможных стратегий:

  • C1 Одна крайность состоит в том, чтобы в качестве дескриптора класса взять его имя. В общем случае этого недостаточно: если имя класса, породившего объект, в сохранившей его системе совпадет с именем класса в системе, возвратившей этот объект, то объект будет принят, даже если эти два класса совершенно несовместимы. Неизбежно последуют неприятности.
  • C2 Другая крайность - использовать в качестве дескриптора класса весь его текст, не обязательно в виде строки, но в некоторой подходящей внутренней форме (дерева абстрактного синтаксиса). Понятно, что с точки зрения эффективности это самое плохое решение: и занимаемая память, и время сравнения дескрипторов максимальны. Но оно может оказаться неудачным и с точки зрения надежности, так как некоторые изменения класса являются безвредными. Предположим, например, что к тексту класса добавилась новая процедура, но атрибуты класса и его инвариант не изменились. Тогда нет ничего плохого в том, чтобы рассматривать возвращаемый объект как соответствующий современным требованиям, а определение его как рассогласованного может привести к неоправданным затруднениям (таким как исключение) в возвращающей системе.
  • C3 Более реалистичный подход состоит в том, чтобы включить в дескриптор класса его имя и список имен атрибутов и их типов. По сравнению с номинальным подходом остается риск того, что два совершенно разных класса могут иметь одинаковые имена и атрибуты, но (в отличие от С1) такие случайные совпадения на практике чрезвычайно маловероятны.
  • C4 Еще один вариант C3 включает не только список атрибутов, но и инвариант класса. Это приведет к тому, что добавление или удаление подпрограммы, не приводящей к рассогласованию объекта, окажется безвредным, так как, если бы изменилась семантика класса, то изменился бы и его инвариант.

C3 - это минимальная разумная политика, и в обычных случаях она представляется хорошим выбором, по крайней мере для начала.
Извещение
Что должно произойти после того, как номинальный или структурный механизм выявления выловит рассогласование объекта?
Хотелось бы, чтобы возвращающая система узнала об этом и сумела предпринять необходимые корректирующие действия. Этой проблемой будет заниматься некоторый библиотечный механизм. Класс GENERAL (предок всех классов) должен содержать процедуру:
correct_mismatch is
do
...См. полную версию ниже...
end
и правило, что любое выявленное рассогласование объекта приводит к вызову correct_mismatch (корректировать_рассогласование) на временно возвратившейся версии объекта. Каждый класс может переопределить стандартную версию correct_mismatch аналогично всякому переопределению процедур создания и стандартной обработки исключений default_rescue. Любое переопределение correct_ mismatch должно сохранять инвариант класса.
Что должна делать стандартная (определенная по умолчанию) версия correct_mismatch? Дать ей пустое тело, демонстрируя ненавязчивость, не годится. Это означало бы по умолчанию игнорирование рассогласования, что привело бы к всевозможным ненормальностям в поведении системы. Для глобальной стандартной процедуры лучше возбудить соответствующее исключение:
correct_mismatch is
-- Обработка рассогласования объекта при возврате
do
raise_mismatch_exception
end
где процедура, вызываемая в теле, делает то, что подразумевается ее именем. Это может привести к некоторым неожиданным исключениям, но лучше это, чем разрешить рассогласованиям остаться незамеченными. Если в проекте требуется переделать это предопределенное поведение, например, выполнять пустую инструкцию, а не возбуждать исключение, то всегда можно переопределить correct_mismatch, на свой страх и риск, в классе ANY. (Как вы помните, определенные разработчиками классы наследуют GENERAL не прямо, а через класс ANY, который может быть переделан при проектировании или инсталляции.)


Для большей гибкости имеется также компонент mismatch_information (информация_о_рассогласовании) типа ANY, определенный как однократная функция. Процедура set_mismatch_information (info: ANY) позволяет передать в correct_mismatch больше информации, например, о различных предыдущих версиях класса.

Если вы предполагаете, что у объектов некоторого класса возникнут рассогласования, то лучше не рассчитывать на обработку исключений по умолчанию, а переопределить correct_mismatch так, чтобы сразу изменять возвращаемый объект. Это приводит нас к последней задаче - исправлению.
Исправление
Как следует исправлять объект, для которого при возвращении обнаружено рассогласование? Ответ требует аккуратного анализа и более сложного подхода, чем обычно реализуется в существующих системах или предлагается в литературе.
Ситуация такова: механизм возвращения (с помощью компонента retrieved класса STORABLE, соответствующей операции БД или другого доступного примитива) создал в возвращающей системе новый объект, исходя из некоторого сохраненного объекта того же класса, но обнаружил при этом рассогласование. Новый объект в его временном состоянии может быть неправильным, например, он может потерять некоторое поле, присутствовавшее у сохраненного объекта, или приобрести поле, которого не было у оригинала. Рассматривайте его как иностранца без визы.


Такое состояние объекта аналогично промежуточному состоянию объекта, создаваемого - вне всяких рассуждений о сохранении - с помощью инструкции создания create x.make (...) сразу после распределения ячеек памяти объекта и инициализации их предопределенными значениями, но перед вызовом make (см.курса "Основы объектно-ориентированного программирования". На этой стадии у объекта имеются все требуемые компоненты, но он еще не готов быть принятым в обществе, поскольку может иметь неверные значения некоторых полей; как мы видели, официальная цель процедуры make состоит в замене при необходимости предопределенных значений инициализации на значения, обеспечивающие инвариант.

Предположим для простоты, что метод выявления является структурным и основан на атрибутах (т. е. на определенной выше политике C3), хотя приведенное далее обсуждение распространяется и на другие решения, как номинальные, так и структурные. Рассогласование является следствием изменения свойств атрибутов класса. Можно свести все такие изменения к комбинациям некоторого числа добавлений и удалений атрибутов. На приведенном выше рисунке показано одно добавление и одно удаление.
Удаление атрибута не вызывает никаких трудностей: если в новом классе отсутствует некоторый атрибут старого класса, то соответствующие поля в объекте больше не нужны и их можно просто убрать. Фактически, процедура correct_mismatch ничего не должна делать с такими полями, поскольку механизм возвращения при создании временного экземпляра нового класса будет их отбрасывать. На рисунке это показано для нижнего поля - скорее, уже не поля - изображенного объекта.


Можно было бы, конечно, проявить больше заботы об отбрасываемых полях. А что, если они были действительно необходимы, а без них объект потеряет свой смысл? В таком случае нужно иметь более продуманную политику выявления, например, такую, как структурная политика C4, которая учитывает инварианты.

Более тонкая вещь - добавление атрибута в новый класс, приводит к появлению нового поля в возвращаемых объектах. Что делать с таким полем? Нужно его как-то инициализировать. В известных мне системах, поддерживающих эволюцию схемы и преобразование объектов, решение состоит в использовании предопределенных значений, заданных по умолчанию (обычно для чисел выбирается ноль, для строк - пустая строка). Но, как следует из обсуждения похожих проблем, возникающих, например, в контексте наследования, это решение может оказаться очень плохим!
Вспомним стандартный пример - класс ACCOUNT с атрибутами deposits_list и withdrawals_list. Предположим, в новой версии добавлен атрибут balance. Система, используя новую версию, пытается возвратить некоторый экземпляр, созданный в предыдущей версии.
Цель добавления атрибута balance понятна: вместо того, чтобы перевычислять баланс счета по каждому требованию, мы держим его в объекте и обновляем при необходимости. Инвариант нового класса отражает это с помощью предложения вида:
balance = deposits_listltotal - withdrawals_listltotal
Но, если применить к полю balance возвращаемого объекта инициализацию по умолчанию, то получится совершенно неправильный результат, в котором поле с балансом счета не согласуется с записями вкладов и расходов. На приведенном рисунке balance из-за инициализации по умолчанию нулевой, а в соответствии со списком вкладов и расходов он должен равняться $1000.
Это показывает важность механизма корректировки correct_mismatch . В данном случае можно просто переопределить эту процедуру:
correct_mismatch is
-- Обработать рассогласование объекта, правильно установив balance
do
balance := deposits_list.total -withdrawals_list.total
end
Если автор нового класса ничего не запланирует на этот случай, то предопределенная версия correct_mismatch возбудит исключение, которое аварийно остановит приложение, если не будет обработано retry (реализующим другую возможность восстановления). Это правильный выход, поскольку продолжение вычисления может нарушить целостность структуры выполняемого объекта и, что еще хуже, структуры сохраненного объекта, например БД. Используя предыдущую метафору, можно сказать, что мы будем отвергать объект до тех пор, пока не сможем присвоить ему надлежащий иммигрантский статус.

От сохраняемости к базам данных

Использование класса STORABLE становится недостаточным для приложений, полностью основанных на БД. Его ограниченность отмечалась уже выше: имеется лишь один входной объект, нет поддержки для запросов, основанных на содержимом, каждый вызов retrieved заново создает всю структуру, без всякого разделения объектов в промежутках между последовательными вызовами. Кроме того, в STORABLE не поддерживается одновременный доступ разных приложений клиента к одним и тем же сохраненным данным.
Хотя различные расширения этого механизма могут облегчить или устранить некоторые из этих проблем, полностью отработанное решение требует отдать предпочтение технологии баз данных.
Набор механизмов, ОО или нет, предназначенных для сохранения и извлечения элементов данных (в общем случае "объектов") заслуживает названия системы управления базой данных (СУБД), если он поддерживает следующие свойства:

  • Живучесть (Persistence): объекты могут пережить завершение отдельных сессий использующих их программ, а также сбои компьютера.
  • Программируемая структура (Programmable structure): система рассматривает объекты как структурированные данные, связанные некоторыми точно определенными отношениями. Пользователи системы могут сгруппировать множество объектов в некоторую совокупность, называемую базой данных, и определить структуру конкретной БД.
  • Произвольный размер (Arbitrary size): нет никаких заранее заданных ограничений (вытекающих, например, из размера основной памяти компьютера или ограниченности его адресного пространства) на число объектов в базе данных.
  • Контроль доступа (Access control): пользователь может "владеть" объектами и определять права доступа к ним.
  • Запросы, основанные на свойствах (Property-based querying): имеются механизмы, позволяющие пользователям и программам находить объекты в базе данных, задавая их абстрактные свойства, а не местоположение.
  • Ограничения целостности (Integrity constraints): пользователи могут налагать некоторые семантические ограничения на объекты и заставлять базу данных поддерживать их выполнение.
  • Администрирование (Administration): доступны средства для осуществления текущего контроля, аудита, архивации и реорганизации БД, добавления и удаления ее пользователей, распечатки отчетов.
  • Разделение (Sharing): несколько пользователей или программ могут одновременно получать доступ к базе данных.
  • Закрытие (Locking): пользователи или программы могут получать исключающий доступ (только для чтения, для чтения и записи) к одному или нескольким объектам.
  • Транзакции (Transactions): можно так определять последовательности операций БД, называемые транзакциями, что либо вся транзакция будет выполнена нормально, либо при неудачном завершении не оставит никаких видимых изменений в состоянии БД.

Стандартный пример транзакции - это перевод денег в банке с одного счета на другой, требующий двух операций - занесения в дебет первого счета и в кредит второго, которые должны либо обе успешно завершиться, либо вместе не выполниться. Если они завершаются неудачей, то всякое частичное изменение, такое как занесение в дебет первого счета, нужно отменить; это называется откатом (rolling back) транзакции.

Приведенный список свойств не является исчерпывающим; он отражает то, что предлагается большинством коммерческих систем и ожидается пользователями.
Объектно-реляционное взаимодействие
Безусловно, сегодня наиболее общей формой СУБД является реляционная (relational) модель, базирующаяся на идеях, предложенных Коддом (E. F. Codd) в статье 1970 года.
Определения
Реляционная БД - это набор отношений (relations), каждое из которых состоит из множества кортежей (tuples) (или записей [records]). Отношения также называются таблицами, а кортежи строками, так как отношения удобно представлять в виде таблиц. Как пример, рассмотрим таблицу BOOKS (КНИГИ):


Таблица 13.1. Отношение КНИГИ (BOOKS)

title (название)

date (дата)

pages (страницы)

author (автор)

"The Red and the Black"

1830

341

"STENDHAL"

"The Charterhouse of Parma"

1839

307

"STENDHAL"

"Madame Bovary"

1856

425

"FLAUBERT"

"Euge_nie Grandet"

1833

346

"BALZAC"

Каждый кортеж состоит из нескольких полей (fields). У всех кортежей одного отношения одинаковое число и типы полей; в примере первое и последнее поля являются строками, а два других - целыми числами. Каждое поле идентифицируется именем: в примере с книгами это title, date и т. д. Имена полей (столбцов) называются атрибутами (attributes).
Реляционные базы обычно являются нормализованными, среди прочего это означает, что каждое поле имеет простое значение (целое число, вещественное число, строка, дата) и не может быть ссылкой на другой кортеж.
Операции
Реляционной модели баз данных сопутствует реляционная алгебра, в которой определено много операций над отношениями. Три типичные операции - это выбор (selection), проекция (projection) и соединение (join).
Выбор выдает отношение, содержащее подмножество строк данного отношения, удовлетворяющее некоторому условию на значения полей. Применяя условие выбора "pages меньше, чем 400" к BOOKS, получим отношение, состоящее из первой, второй и последней строки BOOKS.
Проекция отношения на один или несколько атрибутов получается пропуском всех других полей и устранением повторяющихся строк в получившемся результате. Если спроектировать наше отношение на последний атрибут, то получим отношение с одним полем и с тремя кортежами: "STENDHAL", "FLAUBERT" и "BALZAC". Если же спроектировать его на три первых атрибута, то получится отношение с тремя полями, полученное из исходного вычеркиванием последнего столбца.
Соединение двух отношений это комбинированное отношение, полученное путем выбора атрибутов с согласованными типами в каждом из них и объединением строк с одинаковыми (в общем случае, согласованными) значениями этих атрибутов. Предположим, что у нас имеется еще отношение AUTHORS (АВТОРЫ):


Таблица 13.2. Отношение AUTHORS (АВТОРЫ)

Name (имя)

real_name (настоящее_имя)

Birth (год_ рождения)

death (год_ смерти)

"BALZAC"

"Honore_ de Balzac"

1799

1850

"FLAUBERT"

"Gustave Flaubert"

1821

1880

"PROUST"

"Marcel Proust"

1871

1922

"STENDHAL"

"Henry Beyle"

1783

1842

Тогда соединение отношений BOOKS и AUTHORS по согласованным атрибутам author и name будет следующим отношением:


Таблица 13.3. Соединение отношений BOOKS и AUTHORS по полям author и name

title

date

pages

author/name

real_name

birth

death

"The Red and the Black"

1830

341

"STENDHAL"

"Henry Beyle"

1783

1842

"The Charterhouse of Parma"

1839

307

"STENDHAL"

"Henry Beyle"

1783

1842

"Madame Bovary"

1856

425

"FLAUBERT"

"Gustave Flaubert"

1821

1880

"Euge_nie Grandet"

1833

346

"BALZAC"

"Honore_ de Balzac"

1799

1850

Запросы
Реляционная модель допускает запросы - одно из главных требований к базе данных в нашем списке - в стандартизированном языке, называемом SQL. Они используются в двух формах: одна применяется непосредственно людьми, а другая ("встроенный SQL") используется в программах. В первой форме типичный SQL-запрос выглядит так:
select title, date, pages from BOOKS
Он выдает названия, даты и число страниц для всех книг из таблицы BOOKS. Как мы видели, этот запрос в реляционной алгебре является операцией проекции. Другой пример:
select title, date, pages, author where pages < 400
соответствует в реляционной алгебре выбору. Запрос:
select
title, date, pages, author, real_name, birth, date
from AUTHORS, BOOKS where
author = name
это внутреннее соединение, дающее тот же результат, что и приведенный пример соединения.
Использование реляционных баз данных с ОО-ПО
Основные понятия реляционных СУБД, кратко описанные выше, демонстрируют явное сходство с основной моделью ОО-вычисления. Можно сопоставить отношению класс, а кортежу этого отношения - объект, экземпляр класса. Нам потребуется библиотечный класс, предоставляющий операции реляционной алгебры (соответствующий встроенному SQL).
Многие ОО-окружения предоставляют такую библиотеку для C++, Smalltalk или для языка этой книги (библиотека Store). Этот подход, который можно назвать объектно-реляционным взаимодействием, был успешно испробован во многих разработках. Он подходит в одном из следующих случаев:

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

Если ваши требования к сохраняемости не подпадают под эти случаи, то вы будете испытывать то, что в литературе называют сопротивлением несогласованности (impedance mismatch) между ОО-моделью данных вашей разработки ПО и реляционной моделью данных БД. Тогда полезно будет взглянуть на новейшие разработки в области БД: объектно-ориентированные системы баз данных.

Основания ОО-баз данных

Становление ОО-БД подкреплялось тремя стимулами.

  • D1 Желанием предоставления разработчикам ОО-ПО механизма сохранения объектов, сопоставимого с их методом разработки и устраняющего сопротивление несогласованности.
  • D2 Необходимостью преодоления концептуальных ограничений реляционных баз данных.
  • D3 Возможностью предложения более развитых средств работы с базами данных, отсутствующих в ранних системах (реляционных и других), но сделавшихся возможными и необходимыми благодаря прогрессу технологии.

Первый стимул наиболее очевиден для освоивших объектную разработку ПО, когда они сталкиваются с необходимостью сохранения объектов. Но он не обязательно является самым важным. Два других полностью относятся к области баз данных и не зависят от метода разработки.
Изучение понятия ОО-БД начнем с выявления ограничений реляционных систем D2 и того, чем они могут не устроить разработчиков ОО ПО (D1), а затем перейдем к новаторским достижениям движения за ОО-БД.
На чем застопорились реляционные БД
Было бы абсурдом отрицать вклад систем реляционных БД. (На самом деле в то время как первые публикации по ОО-БД в восьмидесятых склонялись к критике реляционной технологии, современная тенденция состоит в том, чтобы рассматривать эти два подхода как взаимодополняющие.) Реляционные системы являются одним из важнейших компонентов роста информационных технологий, начиная с семидесятых, и будут оставаться им еще долгое время. Они хорошо приспособились к ситуациям, связанным с данными (возможно, больших размеров), в которых:

  • R1 структура данных регулярна: все объекты данного типа имеют одинаковое число и типы компонентов;
  • R2 эта структура простая: для типов компонентов имеется небольшое множество заранее определенных возможностей;
  • R3 эти типы выбираются из небольшой группы заранее определенных возможных типов (целые числа, строки, даты, ...), для каждого из которых фиксированы размеры.

Типичным примером является БД с данными о налогоплательщиках с большим количеством объектов, представляющих людей, описываемых фиксированными компонентами: ФИО (строка), дата рождения (дата), адрес (строка), зарплата (число) и еще несколько свойств.
Свойство (R3) исключает многие приложения, связанные с мультимедиа, CAD-CAM и обработкой изображений, в которых некоторые элементы данных, такие как битовые образы изображений, имеют сильно различающиеся и иногда очень большие размеры. Этому также мешает требование, чтобы отношения находились в "нормальной форме", налагаемое существующими коммерческими системами, из-за которого один объект не может ссылаться на другой. Это, конечно, очень сильное ограничение, если сравнить его с тем, что мы доказали раньше в дискуссиях этой книги.
Как только у нас есть некоторый объект, ссылающийся на другой объект, то ОО-модель обеспечивает простой доступ к непрямым свойствам этого объекта. Например, redblack.author.birth_year возвращает значение 1783, если переменная redblack присоединена к объекту слева на. Реляционное описание неспособно представить поле со ссылкой author (автор), чьим значением является обозначение другого объекта.
В реляционной модели имеется обходной путь для этой проблемы, но он тяжелый и непрактичный. Для представления описанной ситуации будут необходимы два отношения BOOKS и AUTHORS, введенные выше. Для их связывания можно выполнить уже показанную операцию соединения, использующую соответствие между полями author первого отношения и name - второго.
Для ответа на вопросы вида: "В каком году родился автор "Красного и черного"?" реляционная реализация должна будет вычислять соединения, проекции и т. п. В данном случае можно использовать указанное выше соединение, а затем взять его проекцию на атрибут birth.
Этот метод работает и широко используется, но он применим только для простых схем. Число операций соединения быстро возрастает в сложных случаях для систем, постоянно обрабатывающих запросы со многими связями, например: "Сколько комнат имеется в предыдущем доме менеджера отдела, из которого дама, закончившая на первом месте среднюю школу вместе с младшим дядей моей жены, была переведена, когда компания-учредитель провела второй тур реорганизации?" Для ОО-системы, поддерживающей во время выполнения сеть соответствующих объектов, ответ на этот запрос не представляет никакой сложности.
Идентичность объектов
Простота реляционной модели частично объясняется тем, что объекты однозначно идентифицируются значениями своих атрибутов. Отношение (таблица) является подмножеством декартового произведения A x B x ... некоторых множеств A, B, ...; иными словами, каждый элемент отношения, каждый объект, это кортеж <a1, b1, ...>, в котором a1 принадлежит A и т. д. Поэтому он не существует вне своего значения, в частности, вставка объекта в отношение не будет иметь никакого эффекта, если в отношении уже имелся идентичный кортеж. Например, вставка <"The Red and the Black", 1830, 341, "STENDHAL"> в приведенное выше отношение BOOKS не приведет к изменению этого отношения. Это сильно отличается от динамичной модели ОО-вычислений, в которой могут существовать два идентичных объекта.
Напомним, что отношение equal (obj1, obj2) истинно, если obj1 и obj2 - это ссылки, присоединенные к этим объектам, но равенство obj1 = obj2 будет ложным.
Быть идентичными - не значит быть одними и теми же (спросите об этом близнецов). Такая способность различать два этих понятия частично определяет силу моделирования в ОО-технологии. Она основана на понятии идентичности объекта: всякий объект существует независимо от его содержания.


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

Разумеется, в реляционной модели тоже можно выразить идентичность объектов: достаточно добавить к каждому объекту специальное ключевое поле с уникальным для объектов данного типа значением. Но придется об этом заботиться явно. А в ОО-модели идентичность объектов имеется по умолчанию.
При создании ОО-ПО, не требующего хранить объекты, поддержка идентичности объекта получается почти случайно: в простейшей реализации каждый объект постоянно хранится по некоторому адресу, а ссылки на объект используют этот адрес, который служит неизменяемым идентификатором данного объекта. (Это неверно для более сложных реализаций, например, фирмы ISE, которые могут перемещать объекты в процессе эффективного сбора мусора; в этих реализациях идентичность объекта является более абстрактным понятием.) Если требуется хранить объекты, то идентичность объекта становится важным фактором ОО-модели.
Поддержка идентичности объектов в разделяемых БД приводит к новым проблемам: каждый клиент, которому нужно создавать объекты, должен получать для них уникальные идентификаторы; это значит, что специальный модуль, ответственный за присвоение идентификаторов, должен быть разделяемым ресурсом, что в условиях сильной параллельности создает потенциальное узкое горлышко.

 

 
На главную | Содержание | < Назад....Вперёд >
С вопросами и предложениями можно обращаться по nicivas@bk.ru. 2013 г.Яндекс.Метрика