Я пишу набор классов коллекций для разных типов деревьев. Я делаю это как учебное упражнение, и я также надеюсь, что это окажется полезным. Я действительно хочу сделать это правильно, поэтому я читал Effective Java , и я также смотрел на то, как Джошуа Блох реализовал классы коллекции, посмотрев на источник. Кажется, у меня есть четкое представление о том, что делается, но у меня еще есть кое-что, чтобы разобраться.
У меня есть интерфейс Node<T>
и класс AbstractNode<T>
, который реализует интерфейс Node
. Затем я создал GenericNode<T>
(узел, который может иметь от 0 до n потомков, и который является частью n -ary дерева) класса, который расширяет AbstractNode<T>
и реализует Node<T>
. Эта часть была легкой.
Затем я создал интерфейс Tree<T>
и класс AbstractTree<T>
, который реализует интерфейс Tree<T>
. После этого я начал писать класс GenericTree<T>
, который расширяет AbstractTree<T>
и реализует Tree<T>
. Здесь у меня начались проблемы.
Что касается дизайна, GenericTree<T>
может состоять только из узлов типа GenericTreeNode<T>
. Это включает в себя рут. В моем Tree<T>
интерфейсе у меня есть:
public interface Tree<T> {
void setRoot(Node<T> root);
Node<T> getRoot();
List<Node<T>> postOrder();
... rest omitted ...
}
И, AbstractTree<T>
реализует этот интерфейс:
public abstract class AbstractTree<T> implements Tree<T> {
protected Node<T> root;
protected AbstractTree() {
}
protected AbstractTree(Node<T> root) {
this.root = root;
}
public void setRoot(Node<T> root) {
this.root = root;
}
public Node<T> getRoot() {
return this.root;
}
... rest omitted ...
}
В GenericTree<T>
я могу иметь:
public GenericTree(Node<T> root) {
super(root);
}
Но это означает, что вы можете создать общее дерево, используя любой подтип Node<T>
. Вы также можете установить корень дерева для любого подтипа Node<T>
. Я хочу иметь возможность ограничить тип узла типом дерева, которое он может представлять. Чтобы это исправить, я могу сделать это:
public GenericTree(GenericNode<T> root) {
super(root);
}
Однако setRoot
по-прежнему принимает параметр типа Node<T>
. Это означает, что пользователь все еще может создать дерево с неправильным типом корневого узла. Как мне применить это ограничение? Единственный способ, которым я могу думать, это либо:
- Выполните
instanceof
, который ограничивает проверку временем выполнения. Я не большой поклонник этого.
- Удалите
setRoot
из интерфейса и попросите базовый класс реализовать этот метод. Это означает, что он не является частью контракта, и любой, кто хочет создать новый тип дерева, должен помнить о необходимости реализации этого метода.
Есть ли лучший способ?
Второй вопрос, который у меня есть, касается типа возврата postOrder
, который равен List<Node<T>>
. Это означает, что если пользователь работает с GenericTree<T>
объектом и вызывает postOrder
, он или она получает список, состоящий из Node<T>
объектов. Это означает, что при выполнении итерации (с использованием конструкции foreach) им пришлось бы выполнять явное приведение к GenericNode<T>
, если они хотят использовать методы, которые определены только в этом классе. Мне не нравится возлагать это бремя на пользователя. Какие у меня варианты в этом случае? Я могу думать только об удалении метода из интерфейса, и подкласс должен реализовать этот метод, убедившись, что он возвращает список соответствующего подтипа Node<T>
. Однако это еще раз удаляет его из контракта, и любой, кто хочет создать новый тип дерева, должен помнить о реализации этого метода. Есть ли лучший способ?