我正在为不同类型的树编写一组集合类。我这样做是作为一项学习练习,我也希望它能有所帮助。我真的很想以正确的方式做到这一点,所以我一直在阅读 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?
发布评论
评论(1)
我认为你是本末倒置。
实现
Tree
和Node
的几个具体实例。只有在分析它们有哪些共同的实现之后,并且只有在它们在那时仍然有意义的情况下才实现您的Abstract
类。编辑
回答你的第二个问题:
如果简单的
Node
不能在你的Tree
界面中切割它,那么你别无选择,但是声明通用接口的第二个参数并在其上放置边界,就像这样然后 AbstractTree
然后 GenericTree
I think you putting a cart before a horse.
Implement a couple of concrete instances of
Tree<T>
andNode<T>
. Only after that analyze what implementation they have in common and only after that implement yourAbstract
classes if they still make sense at that point.EDIT
To answer your second question:
If simple
Node<T>
does not cut it in yourTree
interface, then you have no choice, but to declare a second parameter to generic interface and put a boundary on it, like thisThen AbstractTree
Then GenericTree