Как новички, мы писали очень большие функции, а затем великие классы Бога . По мере того, как мы совершенствуем свои навыки, наши классы становятся все меньше, но их становится все больше: мы получаем небольшой рой, фокусируем классы, которые сотрудничают для создания окончательной системы. Другими словами, мы переносим сложность с отношений между компонентами класса на отношения между классами. Действительно, большинство шаблонов проектирования просто описывают, как должны быть связаны разные классы.
Мы переносим сложность с отношений между компонентами класса на отношения между классами
У нас есть разные виды отношений между классами: агрегация и состав, однонаправленные и двунаправленные ассоциации. У нас есть ограничения на ассоциации, и у нас могут быть атрибуты, связанные с этими отношениями. Однако у нас нет языковой поддержки отношений.
Это очень отличается в языках метамоделирования или в структурах, таких как 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 nodes Node nodeA = Node() Node nodeB = Node() Node nodeC = Node() // and we should be able to easily navigate the relations assertEquals( 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 relations nodeA.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 insertion assertEquals([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, Технический отчет
Я должен сам лучше изучить эти документы, а затем обязательно найти хороший компромисс, чтобы легче было подумать о некоторых классах проблем, связанных с отношениями и ассоциациями. В конце концов, это цель Турина: быть инструментом, который помогает думать о проблемах и решениях.