Как новички, мы писали очень большие функции, а затем великие классы Бога . По мере того, как мы совершенствуем свои навыки, наши классы становятся все меньше, но их становится все больше: мы получаем небольшой рой, фокусируем классы, которые сотрудничают для создания окончательной системы. Другими словами, мы переносим сложность с отношений между компонентами класса на отношения между классами. Действительно, большинство шаблонов проектирования просто описывают, как должны быть связаны разные классы.
Мы переносим сложность с отношений между компонентами класса на отношения между классами
У нас есть разные виды отношений между классами: агрегация и состав, однонаправленные и двунаправленные ассоциации. У нас есть ограничения на ассоциации, и у нас могут быть атрибуты, связанные с этими отношениями. Однако у нас нет языковой поддержки отношений.
Это очень отличается в языках метамоделирования или в структурах, таких как EMF , которые вместо этого позволяют описывать отношения. При построении сложных моделей, например абстрактных синтаксических деревьев, хорошее представление отношений очень помогает.
Поэтому я рассматриваю возможность добавления поддержки отношений в Турине (мой собственный язык программирования).
Пример: АСТ
Рассмотрим абстрактное синтаксическое дерево. Основное отношение — это композиционное отношение, поэтому каждый узел может содержать ноль или более дочерних узлов, в то время как все узлы имеют один родительский узел (или ни одного, для корня дерева).
Предположим, мы можем представить это отношение следующим образом:
| 
 1 
2 
3 
4 
 | 
relation Ast {    one Node parent    many Node children} | 
В идеале мы хотели бы использовать это так:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
 | 
// We create some nodesNode nodeA = Node()Node nodeB = Node()Node nodeC = Node()  // and we should be able to easily navigate the relationsassertEquals(false, nodeA.parent.isPresent())assertEquals(false, nodeB.parent.isPresent())assertEquals(false, nodeC.parent.isPresent())assertEquals(Collections.emptyList(), nodeA.children)assertEquals(Collections.emptyList(), nodeB.children)assertEquals(Collections.emptyList(), nodeC.children)  // we should also be able to easily set new relationsnodeA.parent.set(nodeB)nodeB.children.add(nodeC)assertEquals(nodeB, nodeA.parent.get())assertEquals(false, nodeB.parent.isPresent())assertEquals(nodeB, nodeC.parent.get())assertEquals([], nodeA.children)// note that we preserved the order of insertionassertEquals([nodeA, nodeC], nodeB.children)assertEquals([], nodeC.children) | 
Таким образом, мы ожидаем полностью двунаправленного отношения: когда мы обновляем одну конечную точку, другая автоматически узнает об изменении:
- если мы установим родителем A быть B, дочерние элементы B будут перечислять A
 - если мы добавим A к дочерним элементам B, родительский элемент будет B
 
И каждый узел может иметь одного родителя так:
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
 | 
Node nodeA = Node()Node nodeB = Node()Node nodeC = Node()  nodeA.parent = nodeB  assertEquals([nodeA], nodeB.children)assertEquals([], nodeC.children)  nodeA.parent = nodeC  assertEquals([], nodeB.children)assertEquals([nodeA], nodeC.children) | 
Бухгалтерия не требуется: у всех участвующих сторон всегда самая актуальная картина. Обычно в Java у вас будет поле parent и список дочерних элементов каждого отдельного узла, а изменение родительского элемента будет означать:
- предупреждая старого родителя, чтобы он мог удалить узел из своих потомков
 - оповещение нового нового родителя, чтобы он мог добавить узел к своим потомкам
 - обновить родительское поле в узле
 
Скучно и подвержено ошибкам. В Турине вместо этого он просто работает из коробки. Несколько конечных точек (таких как дочерние ) рассматриваются как автоматически обновляемые списки, в то время как отдельные конечные точки (например, родительские ) вместо этого рассматриваются как своего рода Необязательные (с возможностью устанавливать значение, а не просто проверять его наличие и читать его) ,
Отношения и подмножества
Это здорово, однако мы хотели бы иметь что-то большее. Мы хотели бы иметь разные специализации этих отношений. Например:
- Метод (который является Узлом) может иметь несколько FormalArguments и один возвращаемый тип (TypeUsage)
 - FormalArgument (который является узлом) имеет один тип (TypeUsage)
 
Теперь мы хотели бы добавить экземпляр FormalArgument в качестве параметра метода и рассматривать его как часть «параметров» и «потомков» этого узла.
Другими словами, «params» будет представлять подмножество «детей» метода. Также «returnType» будет подмножеством (с ровно одним элементом) «потомков» метода.
| 
 1 
2 
3 
4 
 | 
type Method extends Node {   FormalArgument* params = subset of AST.children(parent=this)   TypeUsage returnType = subset of AST.children(parent=this)} | 
Обратите внимание, что я хочу иметь возможность:
- добавить узел в подмножество (например, params). Я должен видеть этот узел как среди параметров и детей
 - добавить узел непосредственно среди детей: я должен видеть узел как часть детей, но не как часть какого-либо подмножества
 
Статус
Это синтаксис, над которым я работаю, и большая часть реализации отношений готова. Время сражаться проверить это!
Я все еще работаю над подмножествами отношений, но многие тесты уже пройдены, и я должен быть готов к их скорейшему принятию.
На данный момент я планирую поддерживать только отношения с двумя конечными точками, которые могут быть как одиночными, так и множественными. В будущем отношения могут содержать значения или иметь дополнительные ограничения, как у нас для полей. Я все еще исследую идею и экспериментирую с тем, как она работает на практике, ведь не так много языков программирования поддерживают отношения 🙂
Несколько ресурсов
Нет, я не первый, кто думает об отношениях как о первоклассных гражданах в объектно-ориентированных языках программирования:
Несколько полезных ссылок, чтобы понять больше на теоретическом уровне:
- Отношения первого класса для объектно-ориентированных языков программирования , кандидатская диссертация Стивена Нельсона
 - Отношения: абстрагирование объектных коллабораций , Balzer и др., Технический отчет
 - Объединение и обобщение отношений в ролевом моделировании данных и навигации , Harkes and Visser, Технический отчет
 
Я должен сам лучше изучить эти документы, а затем обязательно найти хороший компромисс, чтобы легче было подумать о некоторых классах проблем, связанных с отношениями и ассоциациями. В конце концов, это цель Турина: быть инструментом, который помогает думать о проблемах и решениях.