I have tried to design a class for a complete graph.
Needless to say, disadvantages of my current solution are very visible:
dependent inputs have led to verification headaches.
In this code we rely on 2 inputs
set of nodes
andedge map
. This creates the need to add a special verification function calledaddEdgeVerification
to ensure both these data structures are in sync.how to avoid this complexity ofinter-parameter dependency
at the same not burdening the client with headache of providing a complex data structure as input to our code ?forces illegal states when some functions are invoked out of sequence.
This code returns illegal state if
getAdj
is called before complete graph has been constructed. How can thisillegal state issues
be avoided, without the need to force user to provide a complete graph ? Note the name of code isCompleteGraphConstructor
. This means users will make use of our code to construct a complete graph. We dont users to take any effort to construct graph themselves. We want them to provide tiny inputs such as edges and nodes, and we should take the pain to join pieces.
But alternatives are also not free from handicaps:
dumping everything in constructor would make it a god constructor
A constructor can help us reduce/eliminate
illegal state
. But it would result in god constructors. It does not look like a good option, either.
I am looking for recommendations on code architecture.
public class CompleteGraphConstructor<T> {
private final Map<T, HashMap<T, Double>> graph;
private Set<T> nodes;
public CompleteGraphConstructor() {
graph = new HashMap<T, HashMap<T, Double>>();
}
/**
* Add the nodes needed to be part of a graph.
*
* @param nodes the set nodes to add in the graph.
* @return true if nodes list is set, and false if it is not.
*/
public boolean addNodes (Set<T> nodes) {
// prevent overriting the nodes.
if (this.nodes != null) {
/**
* Should exception check be performed inside or outside?
*/
if (nodes == null) throw new NullPointerException("The node cannot be null.");
if (nodes.size() == 0) throw new NullPointerException("The size of node cannot be zero.");
this.nodes = nodes;
return true;
}
return false;
}
/**
* Add all edges of the graph.
*
* @param nodeId if nodeId is not null.
* @param allEdges all the edges of input node
*/
public void addEdges (T nodeId, Map<T, Double> allEdges) {
addEdgeVerification(nodeId, allEdges);
graph.put(nodeId, (HashMap<T, Double>) allEdges);
}
private void addEdgeVerification(T nodeId, Map<T, Double> allEdges) {
if (!nodes.contains(nodeId)) throw new NoSuchElementException("The source node: " + nodeId + " does not exist.");
// making sure that edges include nodes provided in the node Set.
for (T node : allEdges.keySet()) {
if (!nodes.contains(node)) throw new NoSuchElementException("The target node: " + nodeId + " not exist.");
}
// make sure that all the nodes in the edge set are included in allEdges
for (T node : nodes) {
if (node != nodeId) {
if (!allEdges.containsKey(nodeId)) throw new IllegalArgumentException("The input map does not contain all edges. ");
}
}
}
public Map<T, Double> getAdj (T nodeId) {
if (!nodes.contains(nodeId)) throw new NoSuchElementException("The node " + nodeId + " does not exist.");
if (!graph.containsKey(nodeId)) throw new IllegalStateException("The graph is not populated with " + nodeId);
return graph.get(nodeId);
}
}