Отношения

Отношения - это описание некоторых связей, которыми могут быть связаны объекты.
Набор отношений, в которых может участвовать конкретный объект, задается в классах, от которых данный объект наследуется.

В формулировке выше обратите внимание, что классы не могут быть связаны между собой отношениями - классы только задают возможные отношения, которыми могут быть связаны их объекты.

Пример использования отношений

//класс "Питомец"
class Pet

//класс "Человек"
class Human {
	//Отношение "имеет питомца", связывает объекты "Людей" с объектами "Питомцев"
	rel hasPet(Pet) : {1 -> *} ;
}

//Кот Бася, питомец
obj basya : Pet

//Человек Юля
obj yulia : Human {
	//Юля связана с Басей отношением "имеет питомца"
	hasPet(basya);
}

В коде определения отношений представлены классом RelationshipDef
Состав базовой хранимой информации:

  • Имя отношения (RelationshipDef.name)
  • Имя класса-субъекта отношения (RelationshipDef.subjectClassName)
  • Имена классов-объектов отношения (RelationshipDef.objectClassNames)
  • Вид/Характеристика отношения (RelationshipDef.kind)

Утверждения о конкретной связи объектов по отношению представлены классом RelationshipLinkStatement
Состав базовой хранимой информации:

  • Объект, из которого исходит связь (субъект связи) (RelationshipLinkStatement.owner)
  • Имя отношения (RelationshipLinkStatement.relationshipName)
  • Имена связанных объектов (объекты связи) (RelationshipLinkStatement.objectNames)

Основные моменты при работе с отношениями

Типизация отношений

Note

Здесь и далее в этой статье для записи определений конкретных отношений, будем использовать запись вида SubjClass -> relationship(ObjClass1, ... ObjClassN), где SubjClass и ObjClass1-N - классы.
(Классы записываем с большой буквы, и используем ->)

Определение отношения вида A -> rel(B, C) (где классы A, B и C в общем случае различны) означает, что объекты-инстансы класса А (и только они) могут быть связаны отношением rel с объектами-инстансами классов B и C (и только с ними).
Эта типизация строга, т.к. нельзя "запихнуть" в отношение объекты классов, не подходящие ему по типу. Однако, наличие в системе наследования означает, что любые наследники данных классов могут быть связаны данным отношением.

Направленность

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

Схематично это можно представить так:
relationship_demo.png

Note

Здесь и далее для записи отдельных утверждений о связях между объектами будем использовать запись subj => relationship(obj1, ..., objN), где subj, и obj1-N - объекты.
(Объекты записываем с маленькой буквы, и используем =>)

На практике направленность отношений выражается в том, что утверждения a => rel(b) и b => rel(a) различны.

"Арность"

Как можно понять из формулировок выше, в общем случае объектов связи может быть больше одного (сколько угодно), в то время как субъект связи есть только один.
В связи с этим, часто возникает необходимость разделять отношения по их "арности" - количеству объектов, связанных этих отношением. Соответственно, арность отношения равно кол-ву объектов связи + 1 (субъект связи).
Наиболее распространены бинарные отношения ("субъект -> объект") и тернарные отношения ("субъект -> два объекта"). N-арными отношениями называем отношения, чья арность превышает 2 (т.е. все, что больше бинарных)
relationship_arity.png

Упорядоченность

Поскольку для отношения вида A -> rel(B, C) классы B и C в общем случае различны, то становится очевидно, что для соответствующих утверждений становится значимым порядок указания объектов связи: поскольку утверждение a => rel(b, c) в таком случае валидно, а утверждение a => rel(c, b) - невалидно, т.к. не соответствует типизации (если предполагать что a, b и c это объекты-инстансы классов A, B и C соответственно).

Таким образом, отношения, чьи классы-объекты различны, считаются упорядоченными - в записи утверждений о них важен порядок объектов связи.
Упорядоченными также считаются отношения некоторых зависимых типов (см. ниже) - например "Ближе к чем" и "Дальше от чем".

В противном случае отношение считается считается неупорядоченным (если отношение задано так, что все его объекты имеют один и тот же класс).
Из этого следует в т.ч. то, что строго говоря бинарные отношения являются неупорядоченными (вырожденный случай), а также то, что зависимые отношения типа "Между" являются неупорядоченными.

Warning

Я считаю, что описанный подход к [не]упорядоченности наверное не очень хорош - поскольку на данный момент нет возможности задать упорядоченное отношение, чьи объекты связи имеют один и тот же тип.
В будущем, данный механизм стоит переделать так, чтобы упорядоченность считалась поведением по умолчанию для всех отношений, вне зависимости от их типизации, а неупорядоченность нужно было отдельно указывать, и она была бы возможна только для связей, чьи объекты имеют один и тот же тип.

Характеристики отношений

Отношения, помимо своих основных параметров (имени и классов субъектов/объектов) имеют также дополнительные характеристики, которые могут повлиять на их смысловое значение, а также дополнительно ограничить особенности их применения.
Все это в коде задается сложным объектом RelationshipDef.kind, имеющим тип RelationshipKind.

В основе этих дополнительных характеристик лежит разделение отношений на независимые (BaseRelationshipKind) и зависимые/вычисляемые (DependantRelationshipKind)

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

Количественность (Квантификатор)

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

Если говорить по-простому, то это характеристика "один-ко-многим", "многие-ко-многим" и т.п., знакомая нам из проектирования БД и подобного, только выраженная в количественном формате:

  • Поле LinkQuantifier.subjCount указывает на то, сколько субъектов связи может существовать для одного заданного объекта связи.
  • Поле LinkQuantifier.objCount указывает на то, сколько объектов связи может существовать для одного заданного субъекта связи.
    Так, если записать квантификатор в формате {subjCount -> objCount}, то квантификатор {2 -> 1} некоторого отношения A -> rel(B) значит, что объекты класса А могут иметь только одну связь с объектом класса B, но с одним таким объектом класса B могут быть связаны 2 объекта класса А.

Пример использования квантификаторов (и нарушения ограничения, поставленного ими)

//класс "Питомец"
class Pet

//класс "Человек"
class Human {
	//Отношение "имеет питомца", связывает объекты "Людей" с объектами "Питомцев"
	//Имеет квантификатор "один-ко-многим" (здесь * означает неограниченность).
	//Т.е. у питомца может быть только один владелец,
	//но у владельца может быть неограниченное кол-во питомцев.
	rel hasPet(Pet) : {1 -> *} ;
}

//Кот Бася, питомец
obj basya : Pet
//плюшевая альпака, питомец
obj alpaca : Pet

//Человек Юля
obj yulia : Human {
	//Юля связана с Басей отношением "имеет питомца"
	hasPet(basya);
	//Юля также связана этим отношением c альпакой 
	//- это не нарушает квантификатора, т.к. objCount отношения не ограничен
	hasPet(alpaca);
}

//Человек Марат
obj marat : Human {
	//Данное утверждение о связи Марата с Басей отношением "имеет питомца"
	//нарушает квантификатор, поскольку subjCount данного отношения равен 1
	hasPet(basya);
}
Important

Явно задавать квантификаторы можно только для независимых отношений (поле BaseRelationshipKind.quantifier).
При этом, делать это не обязательно, т.к. при неуказании квантификатора отношение считается имеющим квантификатор по умолчанию:

  • Для отношений без шкалы - {* -> *}, т.е. "Многие ко многим", что по сути значит отсутствие количественных ограничений
  • Для отношений со шкалой - в соответствии со смыслом шкалы (см. ниже)

Квантификаторы зависимых отношений вычисляются на основе квантификаторов отношений, от которых они зависят.

Шкала

Независимые бинарные отношения могут задавать т.н. шкалу. Это опциональная характеристика, которая задает дополнительные ограничения на структуры, образованные объектами, связанными данным отношением.
На данный момент в системе поддерживается два вида шкал - линейная и частичная.

Если отношение задает линейную шкалу, это значит, что все объекты, объединенные данным отношением, можно выстроить в линию, где каждый следующий объект указывает связью на предыдущий объект:
relationship_linear_scale.png
Пример использования отношения, задающего линейную шкалу:

//Слово в тексте
class Word {
	obj prop text : string ;
	//Отношение "Идет после предыдущего слова" задает линейную шкалу
	rel isAfter(Word) : Linear ;
}

obj word1 : Word {
	text = "Мама" ;
}

obj word2 : Word {
	text = "мыла" ;
	isAfter(word1) ;
}

obj word3 : Word {
	text = "раму" ;
	isAfter(word2) ;
}

Из данного определения вытекает, что класс-субъект и класс-объект данного отношения одинаковы - поскольку каждый объект в цепочке выступает как в качестве субъекта, так и в качестве объекта.
Также, исходя из данного определения, квантификатор отношения, задающего линейную шкалу, всегда равен "один-к-одному" ({ 1 -> 1 }), т.к. единственный следующий объект может только ссылаться на единственный предыдущий, и наоборот.

Если отношение задает частичную шкалу, это значит, что все объекты, объединенные данным отношением, можно выстроить в древовидную структуру, где каждый объект указывает связью на объект-родитель:
relationship_partial_scale.png
Пример использования отношения, задающего частичную шкалу:

//человек
class Person {
	//Отношение "Является ребенком" задает частичную шкалу
	rel isChildOf(Person) : Partial ;
}

obj mom : Person

obj alice : Person {
	isChildOf(mom) ;
}

obj bob : Person {
	isChildOf(mom) ;
}

По логике, аналогичной линейной шкале, класс-субъект отношения в частичной шкале так же равен классу-объекту.
Квантификатор отношения, задающего частичную шкалу, не фиксирован полностью, но должен иметь значение objCount = 1 (т.е. { ... -> 1 }), поскольку у субъекта-ребенка может быть только один объект-родитель. Квантификатором по умолчанию для него является { * -> 1 }

Warning

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

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

Типы зависимых отношений

Как было сказано выше, зависимые отношения по факту не существуют в данных модели (в модели не может быть соответствующего утверждения RelationshipLinkStatement), а вычисляются на основе других отношений.

В таблице ниже представлены поддерживаемые типы зависимых отношений, требования для их объявления, и их смысл.
Данные типы в коде задаются полем DependantRelationshipKind.type, имеющим тип DependantRelationshipKind.Type - это енам. В таблице, для краткости, связанные енам-константы будут писаться просто в виде Type.<VALUE>.
В данной таблице используются следующие обозначения:

  • Зависимое отношение <type>Rel - конкретное отношение, рассматриваемое в строке таблицы (вместо <type> для понятности подставляется описание типа зависимости)
  • Отношение-основа baseRel - отношение, от которого зависит depRel. Обратите внимание, что baseRel может быть как независимым, так и зависимым от какого-то другого отношения.
  • Корневое отношение rootRel - последнее независимое отношение в цепочке зависимостей, начинающейся с depRel. Если baseRel независимое, то оно же и есть rootRel.
Тип зависимого отношения Когда применимо Как определяется Как вычисляется (в чем смысл)
Противоположное, oppositeRel;
Type.OPPOSITE
baseRel должен быть бинарным; допустим A -> baseRel(B),
тогда B -> oppositeRel(A)
Если a => baseRel(b)
то b => oppositeRel(a)
Транзитивное, transitiveRel;
Type.TRANSITIVE
baseRel должен быть бинарным;
rootRel должен задавать шкалу.
допустим A -> baseRel(A)
тогда A -> transitiveRel(A)
Если a => baseRel(b)
и b => baseRel(c)
то a => transitiveRel(c);

Или если a => baseRel(b)
и b => transitiveRel(c)
то a => transitiveRel(c);
Между _ и _, betweenRel;
Type.BETWEEN
baseRel должен быть бинарным;
rootRel должен задавать шкалу.
допустим A -> baseRel(A)
тогда A -> betweenRel(A, A)
Если a => baseRel(b)
и b => baseRel(c)
Или c => baseRel(b)
и b => baseRel(a)

то b => betweenRel(a, c) и b => betweenRel(c, a)
- неупорядоченное отношение
Ближе к _ чем _, closerRel;
Type.CLOSER
baseRel должен быть бинарным;
rootRel должен задавать шкалу.
допустим A -> baseRel(A)
тогда A -> closerRel(A, A)
Если a => baseRel(b)
и b => baseRel(c)
Или c => baseRel(b)
и b => baseRel(a)

то b => closerRel(a, c)
и b => closerRel(c, a)
- упорядоченное отношение
Дальше от _ чем _, furtherRel;
Type.FURTHER
baseRel должен быть бинарным;
rootRel должен задавать шкалу.
допустим A -> baseRel(A)
тогда A -> furtherRel(A, A)
Если a => baseRel(b)
и b => baseRel(c)
Или c => baseRel(b)
и b => baseRel(a)

то c => furtherRel(a, b)
и a => furtherRel(c, b)
- упорядоченное отношение

Параметризация

TODO

Проекция

Important

Скорее всего, проекция не понадобится вам в работе с этим проектом. Это весьма странная и редко используемая фича, поэтому этот раздел вы скорее всего можете пропустить.
Тем не менее, для полноты информации фича описана.

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

//Класс "Игрушка"
class Toy {
	//отношение "имеет часть"
	rel hasPart(ToyPart) : {1 -> * };
}

//Класс "Часть игрушки"
class ToyPart {
	//отношение "сделано из материала"
	rel isMadeOf(Material) : {* -> 1};
}

//Материал, из которого сделаны части игрушки
class Material

//Кукла, игрушка
obj doll : Toy {
	//Имеет часть - голову
	hasPart(dollHead);
	//Имеет часть - тело
	hasPart(dollBody);
}

//Голова куклы
obj dollHead : ToyPart {
	//Сделана из пластика
	isMadeOf(plastic);
}

//Тело куклы
obj dollBody : ToyPart {
	//Сделано из пластика
	isMadeOf(plastic);
}

obj plastic : Material

В подобной модели, в обычной ситуации, мы можем делать утверждение только об материале отдельных частей куклы. Но что, если мы хотим узнать, сделана ли сама кукла из пластика?
В теории, это можно проверить сложным образом, используя квантор общности (For All ...). Но подобная проверка весьма сложна в записи и понимании. В идеале, мы бы хотели проверить простое утверждение, записанное, например, так: doll => isMadeOf(plastic).
Такое утверждение формально невалидно, но именно для этого существует проекция.

Проекция класса A на класс A' возможна, когда в модели существует единственное отношение projRel (т.н. отношение проекции), удовлетворяющее следующим критериям:

  • Оно имеет типизацию A -> projRel(A')
  • Оно имеет квантификатор, чей subjCount = 1 (т.е. {1 -> ...})

Если проекция класса A на класс A' возможна, то это значит, что объекты класса A могут использоваться в вычислимых утверждениях о связях по отношениям, в типизации которых указан класс A'.
Для этого, каждому объекту a класса A ставится в соответствие множество объектов a'1 - a'N класса A', соединенных с ним отношением projRel. Утверждение о связи для объекта a верно, если оно верно для всех соответствующих объектов a'1 - a'N.

Так, в приведенном примере, класс Toy может быть спроектирован на класс ToyPart, поскольку существует отношение проекции hasPart.
Таким образом, объекту doll ставятся в соответствие объекты dollHead и dollBody, и утверждение doll => isMadeOf(plastic) "раскрывается" в dollHead => isMadeOf(plastic) И dollBody => isMadeOf(plastic).

В общем случае, проекцией могут быть подставлены нескольку участников связи - в этом случае происходит комбинаторный перебор всех конфигураций соответствующих объектов.
Например, допустим есть некоторое отношение X -> rel(Y, Z), и при этом существуют проекции A в Х, B в Y и C в Z.
В этой ситуации, допустим мы хотим проверить утверждение a => rel(b, c), где a проецируется в x1 и x2, b проецируется в y1 и y2, и c проецируется в z1 и z2
В таком случае, будут рассмотрены все конфигурации спроецированного утверждения:

  • x1 => rel(y1, z1)
  • x1 => rel(y1, z2)
  • x1 => rel(y2, z1)
  • x1 => rel(y2, z2)
  • x2 => rel(y1, z1)
  • x2 => rel(y1, z2)
  • x2 => rel(y2, z1)
  • x2 => rel(y2, z2)

И только если каждое из них выполняется, то выполняется исходное утверждение a => rel(b, c).

Important

Утверждения, возможные с помощью проекции, являются вычислимыми так же, как утверждения о зависимых отношениях. Это значит, что соответствующих утверждений (RelationshipLinkStatement) не может быть в самих данных.

Корректность данных (валидация)

Полнота

Класс-субъект отношения (RelationshipDef.subjectClassName) должен присутствовать в модели.

Классы-объекты отношения (RelationshipDef.objectClassNames) должны присутствовать в модели.

Для утверждения о связи объектов отношением, данное отношение должно присутствовать в модели.

Для утверждения о связи объектов отношением, все объекты должны присутствовать в модели.

Валидность

Отношение, объявленное в конкретном классе, не может иметь имя, повторяющее имя одного из отношений, уже определенных в классе или его классах-родителях.

Квантификатор могут задавать только бинарные независимые отношения.

Шкалу могут задавать только бинарные независимые отношения.

Если квантификатор задается отношением, которое задает шкалу, квантификатор должен соответствовать требованиям к данной шкале

  • "один-к-одному" для линейной шкалы
  • "...-к-одному" для частичной шкалы

В цепочке зависимости отношений не может быть рекурсии (отношение не может зависеть от самого себя).

Зависимые отношения должны удовлетворять требованиям по типу зависимости.

Утверждения о связи объектов отношением должны удовлетворять типизации отношения (по кол-ву объектов в связи и по их типу).

Не может быть явно заданных утверждений о зависимых отношениях.

Количество связей по отношениям у объектов должно удовлетворять квантификаторам этих отношений.