2008 г.
Базы данных. Вводный курс
Сергей Кузнецов
Назад Содержание Вперёд
11.3. Ограничения целостности и язык OCL
Как уже отмечалось, в диаграммах классов могут указываться ограничения целостности, которые должны поддерживаться в проектируемой БД. В UML допускаются два способа определения ограничений: на естественном языке и на языке OCL. На рис. 11.13 показана простая диаграмма классов Студент
и Университет
с ограничением, выраженным на естественном языке.
Рис. 11.13. Ограничение, выраженное на естественном языке
В данном случае накладывается ограничение на состояние объектов классов Студент
и Университет
, входящих в один экземпляр ассоциации. Объект класса Студент
может входить в экземпляр связи с объектом класса Университет
только при условии, что размер стипендии данного студента находится в диапазоне, допустимом в данном университете.
11.3.1. Общая характеристика языка OCL
Более точный и лаконичный способ формулировки ограничений обеспечивает язык OCL (Object Constraints Language). Вот общая характеристика этого языка.
Из языка UML58) в OCL заимствованы, в первую очередь, следующие концепции:
- класс, атрибут, операция;
- объект (экземпляр класса);
- ассоциация;
- тип данных (включая набор предопределенных типов Boolean, Integer, Real и String);
- значение (экземпляр типа данных).
Для понимания языка OCL существенны определяемые в UML традиционные для объектных моделей данных различия между объектом некоторого класса и значением некоторого типа:
- объект обладает уникальным идентификатором и может сравниваться с другими объектами только по значению идентификатора; следствием этого является возможность определения операций над множествами объектов в терминах их идентификаторов59);
- объект может быть ассоциирован через бинарную связь60) с другими объектами, что позволяет определить в OCL операцию перехода от данного объекта к связанным с ним объектам;
- в то же время значение является «чистым значением» в том смысле, что:
- при сравнении двух значений проверяются сами эти значения;
- кроме того, значения не могут участвовать в связях, поскольку понятие связи определено только для объектов классов.
В дополнение к скалярным типам данных, заимствованным из UML, в OCL предопределены структурные типы, которые являются разновидностями типов коллекций (collection):
- математическое множество (set), неупорядоченная коллекция, не содержащая одинаковых элементов;
- мультимножество (bag), неупорядоченная коллекция, которая может содержать повторяющиеся элементы-дубликаты;
- последовательность (sequence), упорядоченная коллекция, которая может содержать элементы-дубликаты.61)
В OCL элементами каждого из трех типов коллекций могут быть либо объекты, либо значения.
Язык OCL предназначен, главным образом, для определения ограничений целостности данных, соответствующих модели, которая представлена в терминах диаграммы классов UML. OCL может применяться для определения ограничений, описывающих пред- и постусловия операций классов, и ограничений, представляющих собой инварианты классов. При проектировании реляционных баз данных возможность определения пред- и постусловий операций вряд ли может оказаться существенной62). С точки зрения определения ограничений целостности баз данных более важны средства определения инвариантов классов.
11.3.2. Инвариант класса
Под инвариантом класса в OCL понимается условие, которому должны удовлетворять все объекты данного класса. Если говорить более точно, инвариант класса – это логическое выражение, вычисление которого должно давать true
при создании любого объекта данного класса и сохранять истинное значение в течение всего времени существования этого объекта. При определении инварианта требуется указать имя класса и выражение, определяющее инвариант указанного класса. Синтаксически это выглядит следующим образом:
context <class_name> inv:
<OCL-выражение>
Здесь <class-name>
является именем класса, для которого определяется инвариант, inv
– ключевое слово, говорящее о том, что определяется именно инвариант, а не ограничение другого вида, и context
– ключевое слово, которое говорит о том, что контекстом следующего после двоеточия OCL-выражения являются объекты класса <class-name>
, т. е. OCL-выражение должно принимать значение true
для всех объектов этого класса.
Заметим, что OCL является типизированным языком, поэтому у каждого выражения имеется некоторый тип. Естественно, что OCL-выражение в инварианте класса должно быть логического типа.
В общем случае OCL-выражение в определении инварианта основывается на композиции операций, которым посвящена большая часть определения языка. В спецификации языка эти операции условно разделены на следующие группы:
- операции над значениями предопределенных в UML (скалярных) типов данных;
- операции над объектами;
- операции над множествами;
- операции над мультимножествами;
- операции над последовательностями.
Последовательно обсудим эти группы операций.
Операции над значениями предопределенных типов данных
Полагая очевидной семантику предопределенных скалярных типов данных и операций над ними, ограничимся лишь их перечислением. В OCL поддерживаются следующие заимствованные из определения UML скалярные типы данных: Boolean, Integer, Real и String.
Операции над объектами
В OCL определены три операции над объектами:
- получение значения атрибута;
- переход по соединению,
- вызов операции класса (последняя операция для целей проектирования реляционных БД несущественна).
Для записи этих трех операций используется «точечная нотация». Например, результатом выражения вида
<объект>.<имя атрибута>
является текущее значение атрибута с именем имя атрибута
, если объект имеет такой атрибут. В противном случае использование подобного выражения приводит к возникновению ошибки типа.
Результатом применения к объекту операции перехода по соединению (экземпляру связи-ассоциации) является коллекция, содержащая все объекты, которые ассоциированы с данным объектом через указываемое соединение. Это соединение идентифицируется именем роли, противоположной по отношению к данному объекту. Таким образом, синтаксис выражения перехода по соединению следующий:
<объект>.<имя роли, противоположенной по отношению к объекту>
11.3.3. Операции над множествами, мультимножествами и последовательностями
В OCL поддерживается обширный набор операций над значениями коллекционных типов данных. Обсудим только те из них, которые являются уместными в контексте данной лекции. Синтаксически операции над коллекциями записываются в нотации, аналогичной точечной, но вместо точки используется стрелка (
). Таким образом, общий синтаксис применения операции к коллекции следующий:
<коллекция> <имя операции> (<список фактических параметров>)
Операция select
В OCL определены три одноименных операции select
, которые обрабатывают заданное множество, мультимножество или последовательность на основе заданного логического выражения над элементами коллекции. Результатом каждой операции является новое множество, мультимножество или последовательность, соответственно, из тех элементов входной коллекции, для которых результатом вычисления логического выражения является true
.
Операция collect
Аналогично набору операций select
, в OCL определены три операции collect
, параметрами которых являются множество, мультимножество или последовательность и некоторое выражение над элементами соответствующей коллекции. Результатом является мультимножество для операций collect
, определенных над множествами и мультимножествами, и последовательность для операции collect
, определенной над последовательностью. При этом результирующая коллекция соответствующего типа (коллекция значений или объектов) состоит из результатов применения выражения к каждому элементу входной коллекции. Операция collect
используется, главным образом, в тех случаях, когда от заданной коллекции объектов требуется перейти к некоторой другой коллекции объектов, которые ассоциированы с объектами исходной коллекции через некоторое соединение. В этом случае выражение над элементом исходной коллекции основывается на операции перехода по соединению.
Операции exists, forAll, size
В OCL определены три одноименных операции exists
над множеством, мультимножеством и последовательностью, дополнительным параметром которых является логическое выражение. В результате каждой из этих операций выдается true
в том и только в том случае, когда хотя бы для одного элемента входной коллекции значением логического выражения является true
. В противном случае результатом операции является false
. Операции forAll
отличаются от операций exists
тем, что в результате каждой из них выдается true
в том и только в том случае, когда для всех элементов входной коллекции результатом вычисления логического выражения является true
. В противном случае результатом операции будет false
. Операция size
применяется к коллекции и выдает число содержащихся в ней элементов63).
Операции union, intersect, symmetricDifference
Параметрами двуместных операций union
, intersect
, symmetricDifference
являются две коллекции, причем в OCL операции определены почти для всех возможных комбинаций типов коллекции. Не будем рассматривать все определения этих операций и кратко упомянем только две из них. Результатом операции union
, определенной над множеством и мультимножеством, является мультимножество, т. е. из результата объединения таких двух коллекций дубликаты не исключаются. Результатом же операции union
, определенной над двумя множествами, является множество, т. е. в этом случае возможные дубликаты должны быть исключены.
11.3.4. Примеры инвариантов
В заключение обзора языка OCL приведем примеры четырех инвариантов, выраженных на этом языке. Будем основываться на диаграмме классов, показанной на рис. 11.14.
Рис. 11.14. Диаграмма классов, используемая для примеров на языке OCL
Пример 11.1. Определить ограничение «возраст служащих должен быть больше 18 и меньше 100 лет».
context Служащий inv:
self.возраст > 18 and self.возраст < 100
Условие инварианта накладывает требуемое ограничение на значения атрибута возраст
, определенного в классе Служащий
. В условном выражении инварианта ключевое слово self
обозначает текущий объект класса-контекста инварианта. Можно считать, что при проверке данного условия будут перебираться существующие объекты класса Служащий
, и для каждого объекта будет проверяться, что значения атрибута возраст
находятся в пределах заданного диапазона. Ограничение удовлетворяется, если условное выражение принимает значение true
для каждого объекта класса-контекста.
Пример 11.2. Выразить на языке OCL ограничение, в соответствии с которым в отделах с номерами больше 5 должны работать служащие старше 30 лет.
context Отдел inv:
self.номер 5 or
self.служащий select (возраст 30) size () = 0
В этом случае условное выражение инварианта будет вычисляться для каждого объекта класса Отдел
. Подвыражение справа от операции or
вычисляется слева направо. Сначала вычисляется подвыражение self.служащий
, значением которого является множество объектов, соответствующих служащим, которые работают в текущем отделе. Далее к этому множеству применяется операция select
(возраст 30), в результате которой вырабатывается множество объектов, соответствующих служащим текущего отдела, возраст которых не превышает 30 лет. Значением операции size ()
является число объектов в этом множестве. Все выражение принимает значение true
, если последняя операция сравнения «=0
» вырабатывает значение true
, т. е. если в текущем отделе нет служащих младше 31 года. Ограничение в целом удовлетворяется только в том случае, если значением условия инварианта является true
для каждого отдела.
Тот же инвариант можно сформулировать в контексте класса Сотрудник
:
context Сотрудник inv:
self.возраст > 30 or self.отдел.номер 5
Здесь следует обратить внимание на подвыражение self.отдел.номер 5
. Поскольку отдел
– это имя роли соединения, значением подвыражения self.отдел
является коллекция (множество). Но кратность роли отдел
равна единице, т. е. каждому объекту служащего соответствует в точности один объект отдела. Поэтому в OCL допускается сокращенная запись операции self.отдел.номер
, значением которой является номер отдела текущего служащего.
Пример 11.3. Определить ограничение, в соответствии с которым у каждого отдела должен быть менеджер, и любой отдел должен быть основан не раньше соответствующей компании:
context Отдел inv:
self.служащий exists (должность = "manager") and
self.компания.годОснования self.годОснования
Здесь должность – атрибут класса Служащий
, а атрибуты с именем годОснования
имеются и у класса Отдел
, и у класса Компания
. В условном выражении этого инварианта подвыражение self.служащий exists (должность = "manager")
эквивалентно выражению self.служащий select (должность = "manager") size () > 1
. Если бы в ограничении мы потребовали, чтобы у каждого отдела был только один менеджер, то следовало бы написать ... size () = 1
, и это было бы не эквивалентно варианту с exists
.
Обратите внимание, что в этом случае снова законным является подвыражение self.компания.годОснования
, поскольку кратность роли компания
в ассоциации классов Отдел
и Компания
равна единице.
Пример 11.4. Условие четвертого инварианта ограничивает максимально возможное количество служащих компании числом 1000:
context Компания inv:
self.отдел collect (служащие) size ( ) < 1000
Здесь полезно обратить внимание на использование операции collect
. Проследим за вычислением условного выражения. В нашем случае в классе Компания
всего один объект, и он сразу становится текущим. В результате выполнения операции self.отдел
будет получено множество объектов, соответствующих всем отделам компании. При выполнении операции collect (служащие)
для каждого объекта-отдела по соединению с объектами класса СЛУЖАЩИЕ
будет образовано множество объектов-служащих данного отдела, а в результате будет образовано множество объектов, соответствующих всем служащим всех отделов компании, т. е. всем служащим компании.
11.3.5. Плюсы и минусы использования языка OCL при проектировании реляционных баз данных
Плюсы и минусы использования языка OCL при проектировании реляционных БД очевидны. Язык позволяет формально и однозначно (без двусмысленностей, свойственных естественным языкам) определять ограничения целостности БД в терминах ее концептуальной схемы. Скорее всего, наличие подобной проектной документации будет полезным для сопровождения БД, даже если придется преобразовывать инварианты OCL в ограничения целостности SQL вручную.
К отрицательным сторонам использования OCL относится, прежде всего, сложность языка и неочевидность некоторых его конструкций. Кроме того, строгость синтаксиса и линейная форма языка в некотором роде противоречат наглядности и интуитивной ясности диаграммной части UML. Да, в инвариантах OCL используются те же понятия и имена, что и в соответствующей диаграмме классов, но используются совсем в другой манере. И последнее. Трудно доказать или опровергнуть как предположение, что на языке OCL можно выразить любое ограничение целостности, которое можно определить средствами SQL, так и утверждение, что на языке OCL нельзя выразить такой инвариант, для которого окажется невозможным сформулировать эквивалентное ограничение целостности на языке SQL. Лично мне неизвестны работы, в которых бы сравнивалась выразительная мощность этих языков в связи с ограничениями целостности реляционных БД.
11.4. Получение схемы реляционной базы данных из диаграммы классов UML
Если не обращать внимания на различия в терминологии64), то здесь выполняются практически те же шаги, что и в случае преобразования в схему реляционной БД ER-диаграммы. Поэтому ограничимся только некоторыми рекомендациями, специфичными для диаграмм классов.
Рекомендация 1. Прежде чем определять в классах операции, подумайте, что вы будете делать с этими определениями в среде целевой РСУБД. Если в этой среде поддерживаются хранимые процедуры, то, возможно, некоторые операции могут быть реализованы именно с помощью такого механизма. Но если в среде РСУБД поддерживается механизм определяемых пользователями функций, возможно, он окажется более подходящим.
Рекомендация 2. Помните, что сравнительно эффективно в РСУБД реализуются только ассоциации видов «один ко многим» и «многие ко многим». Если в созданной диаграмме классов имеются ассоциации «один к одному», следует задуматься о целесообразности такого проектного решения. Реализация в среде РСУБД ассоциаций с точно заданными кратностями ролей возможна, но требует определения дополнительных триггеров, выполнение которых понизит эффективность.
Рекомендация 3. Для технологии реляционных БД агрегатные и в особенности композитные ассоциации неестественны. Подумайте о том, что вы хотите получить в реляционной БД, объявив некоторую ассоциацию агрегатной. Скорее всего, ничего.
Рекомендация 4. В спецификации UML говорится о том, что, определяя однонаправленные связи, вы можете способствовать эффективности доступа к некоторым объектам. Для технологии реляционных баз данных поддержка такого объявления вызовет дополнительные накладные расходы и тем самым снизит эффективность.
Рекомендация 5. Не злоупотребляйте возможностями OCL.
Диаграммы классов UML – это мощный инструмент для создания концептуальных схем баз данных, но, как известно, все хорошо в меру.
58 Хотя язык OCL формально считается частью UML, он специфицирован в отдельном документе, в котором присутствуют ссылки на другие части спецификации UML, а также вводятся собственные понятия и определения.
59 Следует заметить, что ни в спецификации UML, ни в описании какой-либо другой объектной модели никогда прямо не говорится, что в операциях над множествами объектов в действительности участвуют идентификаторы объектов. Но другого понимания не существует.
60 Обратите внимание, что хотя, в UML допускаются n
-арные связи, в OCL речь идет только об уже привычном для нас бинарном варианте.
61 В контексте проектирования реляционных БД (если не иметь в виду использование объектно-реляционных СУБД) последняя разновидность типа коллекции является бессмысленной, поскольку в реляционных БД упорядоченность не поддерживается. Поэтому мы не будем обсуждать детали операций над последовательностями.
62 Если снова не иметь в виду использование объектно-реляционных СУБД.
63 Для коллекций значений возможно также применение операций min
, max
и avg
, выдающих минимальное, максимальное и среднее значение элементов коллекции соответственно.
64 Очевидным аналогом класса является тип сущности, а аналогом связи-ассоциации — связь в смысле ER-модели. Кстати, различия и беспорядок в терминологии действительно удручают. В ER-модели связь (relationship) — это ассоциация (association) между двумя типами сущности. В UML ассоциация (association) — это один из видов связи (relationship). Да еще зачем-то в UML введен специальный термин link для обозначения экземпляра ассоциации. И снова хотелось бы использовать в качестве русского эквивалента термин связь, но он уже безнадежно занят, и приходится переводить link как соединение. Это, конечно, не противоречит смыслу, но тоже очень плохо, поскольку в области реляционных БД термин соединение и без этого имеет два разных смысла – операции соединения и соединения с сервером баз данных. Мне очень жаль переводчиков книг, посвященных UML.
Назад Содержание Вперёд