I have a problem and I want to know what is the best way to solve it.
Problem:
I have a Binary Decision Tree. Each leaf node has an object (called Matrix) that stores some information and performs some calculations. At some point during execution, I want to ask Matrix if I should split the leaf node. If yes, I should create two children each has its own Matrix and I should delete the Matrix of current node (because it's no longer a leaf). There are two kinds of Matrices but all leaf nodes use the same kind.
interface Matrix {}
class MatrixA extends Matrix {}
class MatrixB extends Matrix {}
I have come up with different approaches to solve this but I don't know which one is better.
Design #1:
class Node {
Node left, right;
Matrix mat;
MatrixFactory fact;
Node(MatrixFactory fact) {
this.fact = fact;
this.mat = fact.newMatrix();
}
void split() {
if (mat.isSplitNeeded()) {
this.mat = null;
this.left = Node(this.fact);
this.right = Node(this.fact);
}
}
}
The problem is that, It feels like I'm violating Single Responsibility Principle, because the Node is responsible for the structure of tree, the matrix handling, and splitting.
Design #2:
class Node {
Node left, right;
}
class NodeMatrixManager {
Map<Node, Matrix> map;
MatrixFactory fact;
NodeMatrixManager(MatrixFactory fact) {
this.fact = fact;
}
void split(Node node) {
mat = this.map[node];
if (mat.isSplitNeeded()) {
node.left = Node();
node.right = Node();
this.map.remove(node);
this.map.insert(new Pair(node.left, this.fact.newMatrix()));
this.map.insert(new Pair(node.right, this.fact.newMatrix()));
}
}
}
Again I feel I'm violating Single Responsibility Principle, because NodeMatrixManager is both responsible for keeping track of Matrix-Node relationship, and also for splitting.
Design #3:
class Node {
Node left, right;
}
class NodeMatrixManager {
Map<Node, Matrix> map;
Matrix getMat(Node node) {return this.map[node];}
insert(Node node, Matrix mat) {this.map.insert(new Pair(node, mat));}
remove(Node node) {this.map.remove(node);}
}
class Splitter {
NodeMatrixManager manager;
MatrixFactory fact;
Splitter(NodeMatrixManager manager, MatrixFactory fact) {
this.manager = manager;
this.fact = fact;
}
void split(Node node) {
mat = this.manager.getMat(node);
if (mat.isSplitNeeded()) {
node.left = Node();
node.right = Node();
this.manager.remove(node);
this.manager.insert(node.left, this.fact.newMatrix());
this.manager.insert(node.right, this.fact.newMatrix());
}
}
}
In this approach I feel that class NodeMatrixManager is unnecessary, because I separated Matrix from Node so that Node doesn't bear too much responsibility and now Splitter has all the responsibility. So why not just put Matrix back in the Node and avoid unnecessary mapping?
Design #4:
class Node {
Node left, right;
Matrix mat;
Node(Matrix mat) {this.mat = mat;}
}
class Splitter {
MatrixFactory fact;
Splitter(MatrixFactory fact) {
this.fact = fact;
}
void split(Node node) {
if (node.mat.isSplitNeeded()) {
node.mat = null;
node.left = Node(this.fact.newMatrix());
node.right = Node(this.fact.newMatrix());
}
}
}
My problem with this design is that one class (Splitter) is accessing and manipulating information stored in another class (Node). It doesn't feel correct. If I put splitting back in class Node, I'm back where I began.
So what is the best design approach? (sorry for the long question)
void split() {if (mat.isSplitNeeded()) {}}
. The fact tha you check in the splt method if it's needed or don't do anything feel like a code smell IMO. If the split method is called when it should not be called, I would rather have an IllegalStateException. Otherwse I think the solution 1 don't violate the SRP. If you define in the behaviour of your Node Object that he's able to split himself, then it's part of is responsability to handle it.