Implementation issue related to interface implementation

I am writing a collection of collection classes for different types of trees. I am doing this as a training exercise, and I also hope that this proves to be something useful. I really want to do it right, and so I read Effective Java , and I also looked at how Joshua Bloch implemented collection classes by looking at the source. I seem to have a fair idea of ​​what is being done, but I still have something to figure out.

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

Then I created the Tree<T> interface and the AbstractTree<T> class, which implements the Tree<T> interface. After that, I started writing the GenericTree<T> class, which extends AbstractTree<T> and implements Tree<T> . Here I began to experience problems.

As for design, GenericTree<T> can consist only 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 this means that you can create a common tree using any subtype of Node<T> . You can also set the root of the tree for any subtype of Node<T> . I want to be able to restrict the type of node to the type of 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> . This means that the user can still create a tree with the wrong root type node. How to enforce this restriction? The only way I can think of is to either:

  • Make instanceof , which limits the check to runtime. I am not a big fan of this.
  • Remove setRoot from the interface and the base class implements this method. This means that it is not part of the contract, and anyone who wants to create a new type of tree should remember to implement this method.

Is there a better way?

The second question I have is regarding the postOrder return postOrder , which is List<Node<T>> . This means that if the user works with the GenericTree<T> object and calls postOrder , he gets a list consisting of Node<T> objects. This means that when iterating through (using the foreach construct), they would cast explicitly to the GenericNode<T> if they wanted to use methods that are defined only in this class. I do not like to put this burden on the user. What are my options in this case? I can only think about removing the method from the interface, and subclass implement this method, making sure that it returns a list of the corresponding subtype Node<T> . However, this once again removes it from the contract, and anyone who wants to create a new type of tree should remember to implement this method. Is there a better way?

+4
source share
1 answer

I think you put the cart before the horse.

Run some specific instances of Tree<T> and Node<T> . Only after that, analyze what kind of implementation do they have together, and only after that will your Abstract classes implement, if they still make sense at this point.

EDIT

To answer the second question:

If a simple Node<T> does not cut it in your Tree interface, then you have no choice but to declare a second parameter for the general interface and put a border on it, for example,

 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 ( ) { ... } ... } 
+4
source

Source: https://habr.com/ru/post/1310916/


All Articles