Implementation question involving implementing an interface
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 for开发者_运维知识库each 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?
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 ( )
{
...
}
...
}
精彩评论