This is a little discussion/example of design for modularity and re-use, inspired by javaAddict and Orlando Augusto posts in their thread “Dynamic Multidimensional Array Printing” https://www.daniweb.com/programming/software-development/code/305580/dynamic-multidimensional-array-printing
That thread has 3 solutions to printing the contents of a multi-dimensional array of arbitrary dimensions - although in fact all the solutions will work for any structure of arrays and scalars within arrays, regardless of shape or size.
But really there are two things going on here
- recursively traverse all the elements within the arbitrarily nested arrays or scalars
- print the contents with nice layout/indentation.
I think it’s worth the extra effort to separate those two things, and thus generate simpler code, with greater opportunities for code re-use. The model I will use is the same as NIO’s Files.walkFileTree
. It’s known as the Visitor Pattern, and many UML parsers use it as well. You have one class that handles traversing (“walking”) the data structure, calling methods in a second class (“Visitor”) for each thing it finds.
In this case the Visitor is
public interface Visitor {
// called at the start of each array, passing its depth (0 for top level)
void arrayStart(int depth);
// called for each element in each array,
// areMoreElements is true if this is not the last element in this array
void element(Object element, boolean areMoreElements);
// called at the end of each array
void arrayEnd();
}
So a trivial example of an implementation would be
class TrivialPrinter implements Visitor {
@Override
public void arrayStart(int depth) {
System.out.print("[");
}
@Override
public void element(Object element, boolean areMoreElements) {
System.out.print(element);
if (areMoreElements) {
System.out.print(", ");
}
}
@Override
public void arrayEnd() {
System.out.print("]");
}
}
The walker has a constructor that takes the Visitor as a parameter, and one public method public void walk(Object nestedArrays)
, e.g.
new ArrayWalker(new TrivialPrinter()).walk(obj2);
In effect this creates a pluggable print formatter, that separates the slightly tacky code needed to indent properly from the slightly tacky code that walks the arrays. Here’s a Visitor that indents nicely:
class IndentedPrinter implements Visitor {
private String indent = "";
private boolean afterElements = false;
@Override
public void arrayStart(int depth) {
if (indent.length() >= 0) {
System.out.println();
}
System.out.print(indent + "[");
indent += " ";
}
@Override
public void element(Object element, boolean isMore) {
System.out.print(element);
if (isMore) {
System.out.print(", ");
}
afterElements = true;
}
@Override
public void arrayEnd() {
indent = indent.substring(2);
if (afterElements) {
System.out.print("]");
} else {
System.out.println();
System.out.print(indent + "]");
}
afterElements = false;
}
}
If you don’t like that formatting, just write another Visitor.
But wait (as Jobs used to say) there’s more thing…
the Walker can be used for all kinds of uses depending on the Visitor, e.g.
class Analyser implements Visitor {
@Override
public void arrayStart(int depth) {}
@Override
public void element(Object element, boolean areMoreElements) {
// look for largest / smallest / some specified element
// accumulate statistics - sum / count / average etc
}
@Override
public void arrayEnd() {}
}
Or, you could write a similar walker but for arbitrarily nested Collections, but still use the same visitor to do the formatted printing.
The complete runnable code is attached, so comments please?
JC