涉及实现接口的实现问题

发布于 2024-09-03 03:24:53 字数 2667 浏览 2 评论 0 原文

我正在为不同类型的树编写一组集合类。我这样做是作为一项学习练习,我也希望它能有所帮助。我真的很想以正确的方式做到这一点,所以我一直在阅读 Effective Java 我也一直在研究 Joshua Bloch 通过查看源代码实现集合类的方式。我似乎对正在做什么有一个大致的了解,但我仍然有一些事情需要解决。

我有一个 Node 接口和一个实现 Node 接口的 AbstractNode 类。然后,我创建了一个 GenericNode (一个可以有 0 到 n 个子节点的节点,它是 n 树的一部分) 扩展 AbstractNode 并实现 Node 的类。这部分很容易。

接下来,我创建了一个 Tree 接口和一个实现 Tree 接口的 AbstractTree 类。之后,我开始编写一个 GenericTree 类,它扩展 AbstractTree 并实现 Tree。这就是我开始遇到问题的地方。

就设计而言,GenericTree 只能由 GenericTreeNode 类型的节点组成。这包括根。在我的 Tree 接口中:

public interface Tree<T> {

    void setRoot(Node<T> root);

    Node<T> getRoot();

    List<Node<T>> postOrder();

    ... rest omitted ...
}

并且 AbstractTree 实现了此接口:

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 中,我可以有:

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

但这意味着您可以使用 Node 的任何子类型创建通用树。您还可以将树的根设置为 Node 的任何子类型。我希望能够将节点的类型限制为它可以表示的树的类型。要解决此问题,我可以这样做:

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

但是,setRoot 仍然接受 Node 类型的参数。这意味着用户仍然可以创建根节点类型错误的树。我如何强制执行此约束?我能想到的唯一方法是:

  • 执行一个 instanceof ,将检查限制为运行时。我不太喜欢这个。
  • 从接口中删除 setRoot 并让基类实现此方法。这意味着它不是合同的一部分,任何想要制作新型树的人都需要记住实现此方法。

有更好的办法吗?

我的第二个问题涉及 postOrder 的返回类型,即 List>。这意味着,如果用户正在操作 GenericTree 对象并调用 postOrder,他或她会收到一个由 Node< 组成的列表/代码> 对象。这意味着在迭代(使用 foreach 构造)时,如果他们想要使用仅在该类中定义的方法,则必须执行显式转换为 GenericNode。我不喜欢把这种负担加在用户身上。在这种情况下我有什么选择?我只能考虑从接口中删除该方法,并让子类实现该方法,以确保它返回 Node 的适当子类型列表。然而,这再次将其从合约中删除,任何想要创建新型树的人都必须记住实现此方法。有更好的办法吗?

I'm writing a set of collection classes for different types of Trees. I'm doing this as a learning exercise and I'm also hoping it turns out to be something useful. I really want to do this the right way and so I've been reading Effective Java and I've also been looking at the way Joshua Bloch implemented the collection classes by looking at the source. I seem to have a fair idea of what is being done, but I still have a few things to sort out.

I have a Node<T> interface and an AbstractNode<T> class that implements the Node interface. I then created a GenericNode<T> (a node that can have 0 to n children, and that is part of an n-ary tree) class that extends AbstractNode<T> and implements Node<T>. This part was easy.

Next, I created a Tree<T> interface and an AbstractTree<T> class that implements the Tree<T> interface. After that, I started writing a GenericTree<T> class that extends AbstractTree<T> and implements Tree<T>. This is where I started having problems.

As far as the design is concerned, a GenericTree<T> can only consist of nodes of type GenericTreeNode<T>. This includes the root. In my Tree<T> interface I have:

public interface Tree<T> {

    void setRoot(Node<T> root);

    Node<T> getRoot();

    List<Node<T>> postOrder();

    ... rest omitted ...
}

And, AbstractTree<T> implements this interface:

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 ...
}

In GenericTree<T>, I can have:

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

But what this means is that you can create a generic tree using any subtype of Node<T>. You can also set the root of a tree to any subtype of Node<T>. I want to be able to restrict the type of the node to the type of the tree that it can represent. To fix this, I can do this:

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

However, setRoot still accepts a parameter of type Node<T>. Which means a user can still create a tree with the wrong type of root node. How do I enforce this constraint? The only way I can think of doing is either:

  • Do an instanceof which limits the check to runtime. I'm not a huge fan of this.
  • Remove setRoot from the interface and have the base class implement this method. This means that it is not part of the contract and anyone who wants to make a new type of tree needs to remember to implement this method.

Is there a better way?

The second question I have concerns the return type of postOrder which is List<Node<T>>. This means that if a user is operating on a GenericTree<T> object and calls postOrder, he or she receives a list that consists of Node<T> objects. This means when iterating through (using a foreach construct) they would have perform an explicit cast to GenericNode<T> if they want to use methods that are only defined in that class. I don't like having to place this burden on the user. What are my options in this case? I can only think of removing the method from the interface and have the subclass implement this method making sure that it returns a list of appropriate subtype of Node<T>. However, this once again removes it from the contract and it's anyone who wants to create a new type of tree has to remember to implement this method. Is there a better way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

樱桃奶球 2024-09-10 03:24:53

我认为你是本末倒置。

实现 TreeNode 的几个具体实例。只有在分析它们有哪些共同的实现之后,并且只有在它们在那时仍然有意义的情况下才实现您的Abstract类。

编辑

回答你的第二个问题:

如果简单的Node不能在你的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 ( )
  {
    ...
  }
  ...
}

I think you putting a cart before a horse.

Implement a couple of concrete instances of Tree<T> and Node<T>. Only after that analyze what implementation they have in common and only after that implement your Abstract classes if they still make sense at that point.

EDIT

To answer your second question:

If simple Node<T> does not cut it in your Tree interface, then you have no choice, but to declare a second parameter to generic interface and put a boundary on it, like this

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

    void setRoot(TNode root);

    TNode getRoot();

    List<TNode> postOrder();

    ... rest omitted ...
}

Then AbstractTree

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

  protected TNode root;

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

  ...
}

Then GenericTree

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

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

  @Override
  public List< GenericNode< T > > postOrder ( )
  {
    ...
  }
  ...
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文