Вопрос реализации, включающий реализацию интерфейса - PullRequest
4 голосов
/ 26 мая 2010

Я пишу набор классов коллекций для разных типов деревьев. Я делаю это как учебное упражнение, и я также надеюсь, что это окажется полезным. Я действительно хочу сделать это правильно, поэтому я читал 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>. Однако это еще раз удаляет его из контракта, и любой, кто хочет создать новый тип дерева, должен помнить о реализации этого метода. Есть ли лучший способ?

1 Ответ

4 голосов
/ 26 мая 2010

Я думаю, ты ставишь телегу перед лошадью.

Реализация пары конкретных экземпляров Tree<T> и Node<T>. Только после этого проанализируйте, какая у них общая реализация, и только после этого реализуйте ваши Abstract классы, если они все еще имеют смысл на этом этапе.

EDIT

Чтобы ответить на ваш второй вопрос:

Если простой Node<T> не обрезает его в вашем Tree интерфейсе, то у вас нет выбора, кроме как объявить второй параметр универсальному интерфейсу и установить для него границу, как это

public interface Tree<
  T,
  TNode extends Node< T >
>
{

    void setRoot(TNode root);

    TNode getRoot();

    List<TNode> postOrder();

    ... rest omitted ...
}

Тогда AbstractTree

public abstract class AbstractTree<
  T,
  TNode extends Node< T >
> implements Tree<T, TNode> {

  protected TNode root;

  protected AbstractTree(TNode root) {
    this.root = root;
  }

  ...
}

Тогда GenericTree

public class GenericTree< T >
  extends AbstractTree< T, GenericNode< T > >
{

  public GenericTree ( GenericNode< T > root )
  {
    super( root );
  }

  @Override
  public List< GenericNode< T > > postOrder ( )
  {
    ...
  }
  ...
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...