Preamble: Is it bad design for a base class to be aware of and make interactions using derived types? I’m assuming not, so what approach should I consider for the following?
(Language is PHP, but I think this question is more concerned with the broader subject of design patterns)
I’ve been having a dilemma trying to model a set of classes to act as nodes; I keep second guessing my design decisions, and it’s leading to perpetual frustration.
Given this set of parameters:
- Nodes have parent references (one way traversal)
- Any object type derived of Node may be a parent (or child) to any other object type derived of Node.
So I’ve got:
abstract class AbstractNode{
protected $_parent;
public function __construct(self $parent = null){
$this->_parent = $parent;
}
public function get_parent(){
return $this->_parent;
}
}
class NodeOne extends AbstractNode{ }
class NodeTwo extends AbstractNode{ }
// more derivatives
Now here’s where my design dilemma comes in; during traversal NodeOne instances may need to be found, by themselves and any other instances of types derived of AbstractNode (note that this functionality isn’t exclusive to NodeOne instances, but this is just an example)
This will allow a type-specific traversal to, for instance, aggregate data from objects of a specific type up the tree. I figured I’d specialize a method to serve this purpose:
public function get_node_one_ancestor(){
if($this->_parent instanceof NodeOne){
return $this->_parent;
}
if(null !== $this->_parent){
return $this->_parent->get_node_one_ancestor();
}
return null;
}
Since any derived type may need to traverse instances NodeOne, it would make sense to plop this method in the AbstractNode base class, however now my base class requires awareness of a derived type.
I think this smells bad, but I don’t know where else this method should go. I’m reading on structural design patterns for possible solutions.
An analogy that comes to mind is the DOM, doing ancestor traversal for certain types:
<root>
<foo id="1">
<bar id="2"></bar>
<bar id="3">
<foo id="4">
<bar id="5">
<foo id="6">
<bar id="7"></bar>
</foo>
</bar>
<bar id="8"></bar>
</foo>
</bar>
</foo>
</root>
-
From
bar[@id='8']aggregate allfooancestoridvalues:
Result4 1 -
From
bar[@id='7']aggregate allfooancestoridvalues:
Result6 4 1
You should be able to generalize it:
To me, it seems like this is something that could live in an external iterator object, but I cannot say that I gave much thought before making this post…