Reputation: 771
I am designing some java objects to represent graphs and trees. For my use case I will be using both data types but I also want my graph algorithms to work on my trees.
import java.util.List;
public interface Node<T> {
T getValue();
List<? extends Node<T>> getNeighbors();
void addNodes(List<? extends Node<T>> nodes);
}
public interface TreeNode<T> extends Node<T> {
List<? extends TreeNode<T>> getChildren();
void addChildren(List<? extends TreeNode<T>> treeNodes);
@Override
default List<? extends Node<T>> getNeighbors() {
return getChildren();
}
@Override
default void addNodes(List<? extends Node<T>> nodes) {
if(nodes.getClass().isInstance(getChildren().getClass())) {
addChildren((List<? extends TreeNode<T>>) nodes);
} else {
throw new RuntimeException("Type error!");
}
}
}
My question is about how I'm dealing with addNodes method in the Node interface in the TreeNode interface. The addNodes method has to be in the Node interface because I want to allow people to write code that can add nodes to graphs. However, I also don't want people to add arbitrary nodes to a tree node(for example adding a graph node to a tree node).
In order to prevent this, I'm checking the type of nodes at runtime and throwing an exception if the type is not right. I'm just wondering if this is the best way to accomplish what I want or if there is a better practice?
Thanks for helping :)
Upvotes: 0
Views: 335
Reputation: 14328
The way I see it, Node
is a container for some data. Tree
and Graph
are two ways to maintain relationships between Nodes. So perhaps three classes should be defined:
import java.util.List;
public class Node<T> {
private T value;
public Node(T value) { this.value = value; }
T getValue() { return value; }
}
public abstract class Tree<T> {
private Node<T> root;
public abstract List<? extends Node<T>> getChildren();
public abstract void addChildren(List<? extends Node<T>> nodes);
public Tree(Node<T> root) { this.root = root; }
}
public abstract class Graph<T> {
private Node<T> root;
public abstract List<? extends Node<T>> getNeighbors();
public abstract void addNeighbors(List<? extends Node<T>> nodes);
public Graph(Node<T> root) { this.root = root; }
}
EDIT: If you want to have shared traversal algorithms, you can put them in separate classes and have the Tree and Graph use similar semantics like this:
// common semantics for node containers
public interface NodeContainer<T> {
List<? extends Node<T>> getRelatedNodes();
}
public abstract class Tree<T> implements NodeContainer<T> {
... // same as above
@Override
public List<? extends Node<T>> getRelatedNodes() {
return getChildren();
}
}
public class NodeContainerTraversal {
public void bfs (NodeContainer<?> container) {
...
}
}
Upvotes: 0
Reputation: 60957
If I understand correctly, what you want is (a variation on) the so-called curiously recurring template pattern. The Node
type needs to be parameterized not only by its payload type (T) but also by the type of nodes it can be used with. So you want something like:
public interface Node<T, N extends Node<T, N>> {
T getValue();
List<N> getNeighbors();
void addNodes(List<N> nodes);
}
public interface TreeNode<T> extends Node<T, TreeNode<T>> {
List<TreeNode<T>> getChildren();
void addChildren(List<TreeNode<T>> treeNodes);
@Override
default List<TreeNode<T>> getNeighbors() {
return getChildren();
}
@Override
default void addNodes(List<TreeNode<T>> nodes) {
addChildren(nodes);
}
}
Demo (shows compilation only): https://ideone.com/44qrmX
Upvotes: 2