Я думаю, что подход через "BinaryTree" предполагает слишком много фиксированной структуры и излишне определяет ваш интерфейс неуниверсальным образом. Это затрудняет повторное использование алгоритмов, поскольку ваше дерево расширяется в недвоичные формы. Вместо этого вам нужно будет кодировать свои интерфейсы для нескольких стилей, когда это не нужно или не нужно.
Кроме того, кодирование с проверками hasLeft / hasRight означает, что каждый доступ является двухэтапным процессом. Проверка существования фиксированной позиции не обеспечит эффективные алгоритмы. Вместо этого, я думаю, вы обнаружите, что добавление универсального свойства, которое может быть двоичным слева / справа или двоичным красным / черным или индексом символа или чем-то еще, позволит гораздо больше повторного использования ваших алгоритмов и проверки того, что данные могут выполняться только этими алгоритмами это нужно (конкретные двоичные алгоритмы).
В семантическом представлении вы хотите сначала закодировать некоторые базовые свойства, а затем специализироваться. Когда вы находитесь «в» узле внутри алгоритма, вы хотите сначала найти дочерние элементы dge . Это должен быть контейнерный диапазон структур ребер, которые позволяют вам переходить к дочерним узлам. Поскольку это может быть универсальный контейнер, в нем может быть 0, 2, 5, 1 или даже 100 ребер. Многим алгоритмам все равно. Если он имеет 0, перебор диапазона ничего не даст - не нужно проверять hasX или hasY. Для каждого ребра вы должны иметь возможность получить дочерний узел и выполнять поиск по любому алгоритму, который вы пожелаете.
Этот подход в основном используется в своей библиотеке графов и позволяет расширять алгоритмы дерева до графов, где они применимы, для еще большего повторного использования универсального алгоритма.
Так что у вас уже есть базовый интерфейс с этим
TreeNode:
getChildEdges: () -> TreeEdgeRange
TreeEdge:
getChildNode: () -> TreeNode
и все, что вам нравится в вашем любимом языке. Например, D имеет особенно полезный синтаксис диапазона.
Вы захотите иметь некоторый базовый объект Tree, который дает вам узлы. Что-то вроде
Tree:
getTreeNodes: () -> TreeNodeRange
начинает вас.
Теперь, если вы хотите поддерживать BinaryTrees, сделайте это как ограничение для этого интерфейса. Обратите внимание, что на самом деле вам не нужны никакие новые методы интерфейса, вам просто нужно применять больше инвариантов - каждый TreeNode имеет 0, 1 или 2 childEdges. Просто создайте тип интерфейса, который указывает на это семантическое ограничение:
BinaryTree : Tree
И если вы хотите поддерживать корневые деревья, добавьте интерфейсный слой с
RootedTree : Tree:
getRoot: () -> TreeNode
добавляет эту возможность.
Основная идея заключается в том, что вам не нужно добавлять методы интерфейса для добавления семантических требований, если вы делаете свои классы более специфичными в иерархии. Добавляйте методы интерфейса только в том случае, если есть новое семантическое поведение, к которому требуется доступ. В противном случае - применять новые инварианты в универсальном интерфейсе.
В конце концов, вы захотите украсить узлы и ребра структурами, в которых хранятся данные об узле или ребре, чтобы вы могли строить деревья трисов и красно-черных, а также все замечательные инструменты продвинутой алгоритмики. Таким образом, вы хотите иметь
PropertiedTreeNode<Property> : TreeNode:
getProperty: () -> Property
PropertiedTreeEdge<Property> : TreeEdge:
getProperty: () -> Property
Поскольку вам нужно разрешить работу с общими алгоритмами, информация о типе того, является ли свойство частью дерева или нет, должна быть общей, и что-то, что алгоритмы могут игнорировать. Это ставит вас на путь развития, где эти проблемы были решены очень элегантно. Я бы порекомендовал изучить эту библиотеку, если вам нужны идеи о том, как построить библиотеку алгоритмов общего дерева.
Если вы следуете указанным выше правилам приравнивания типов к семантическим описаниям, то ваше поддерево должно быть очевидным - оно точно того же типа, что и дерево, из которого оно исходит! На самом деле у вас вообще не должно быть типа SubTree. Вместо этого вы должны просто иметь метод определенного типа TreeNode, с которым вы имеете дело
PropertiedTreeNode<Property>:
getSubTree: () -> PropertiedTree<Property>
И, как и в Boost, когда вы кодируете больше информации о возможностях Tree в его общих свойствах, вы можете получать новые типы Tree с более широкими интерфейсными контрактами.