Depends of course what your design goals are. If your goal is that the tree/graph should be modifiable at any point in time, then a leaf can actually become a parent node. In this case, it is OK to define hierarchy-relevant methods in the component.
Another approach (although I do not know if this is applicable to your use-case) is to use following two ideas:
- Make the structure immutable
- Separate structure from function
By making the structure immutable, we can push all graph construction to, well, the constructors. With this we sidestep the type-casting issue you mentioned, and also make the whole thing easier to reason about.
By separating the structure from function, we don't need to publish the structural information at all to clients, we instead offer the functionality we want to offer. With this we can keep Liskov, Demeter and the other OO things.
This is how it can look like:
public interface Node {
// Here we offer the "functionality", but we don't
// publish the "structure". This is made-up, I
// don't know your use-case.
void process(Consumer<Payload> payloadConsumer);
}
public class Leaf implements Node {
private Payload payload;
public Lead(Payload payload) {
this.payload = payload;
}
@Override
public void process(Consumer<Payload> payloadConsumer) {
payloadConsumer.accept(payload);
}
}
public class ParentNode implements Node {
private List<Node> children;
public ParentNode(Node... children) {
this.children = asList(children);
}
// Here we implement the processing recursively.
// All children can respond according to their semantics,
// instead of assuming any structure beyond what we know.
@Override
public void process(Consumer<Payload> payloadConsumer) {
children.forEach(child -> child.process(payloadConsumer));
}
}
Of course you can define your own Node
types depending on what kind of logic you want to represent. You can define multiple operations, not just the one process()
method I made up. Then you can plug all that together like this:
Node graph = new ParentNode(
new ParentNode(new Leaf(p1), new Leaf(p2)),
new SpecialLeaf(a, b, c) // Whatever
);