So you have a tree whose nodes are of two different types deriving from the same base, and you want to perform an operation on one type but not the other ... this sounds like a job for the visitor pattern. :)
The idea behind the visitor^ pattern is this: it provides a way for elements of complex structures (trees, graphs) to be operated on differently according to their type (which is known only at runtime), and where the specific operation itself may also be known only at runtime, without having to change the whole hierarchy the elements belong to (i.e. avoiding things like the "error" function implementation of addToBalance that you thought of). (^It has little to do with visiting, so it's probably misnamed - it is more of a way of achieving double dispatch for languages that don't natively support it.)
So you can have a collection of operations to perform on the elements, and the operations could be e.g. overloaded based on the type of the element. An easy way to do this is to define a base class for all operations (I call this the Visitor class below). The only thing it will contain are empty functions - one for each type of element an operation could potentially be performed on. These functions will be overridden by specific operations.
class Visitor {
virtual void Visit(ParentAccount*) { /* do nothing by default*/ }
virtual void Visit(ChildAccount*) { /* do nothing by default */ }
};
Now we create a specific class to perform AddToBalance on ChildAccount
s only.
class AddToBalance : public Visitor {
public:
AddBalance(string _nameOfTarget, int _balanceToAdd) :
nameOfTarget(_nameOfTarget), balanceToAdd(_balanceToAdd) {}
void Visit(ChildAccount* _child) { //overrides Visit only for ChildAccount nodes
if(child->name == _name)
child->addToBalance(_balance); //calls a function SPECIFIC TO THE CHILD
}
private:
string nameOfTarget;
int _balanceToAdd;
};
Some changes to your original Account class.
class Account{
vector<Account*> children; //assume ALL Account objects could have children; \
//for leaf nodes (ChildAccount), this vector will be empty
string name;
virtual int getBalance() =0; //generic base class has no implementation
//no addToBalance function!
virtual void Accept(Visitor* _visitor) {
_visitor->Visit(this);
}
};
Notice the Accept() function in the Account class, which simply takes a Visitor* as an argument and calls that visitor's Visit function on this
. This is where the magic happens. At this point, the type of this
as well as the type of the _visitor
will be resolved. If this
is of type ChildAccount and _visitor
is of type AddToBalance
, then the Visit
function that will be called in _visitor->Visit(this);
will be void AddToBalance::Visit(ChildAccount* _child)
.
Which just so happens to call _child->addToBalance(...);
:
class ChildAccount : public Account{
int balance;
virtual int getBalance() { return balance; }
virtual void addToBalance(int amount) {
balance += amount;
}
};
If this
in void Account::Accept()
had been a ParentAccount
, then the empty function
Visitor::Visit(ParentAccount*)
would have been called since this function is not overridden in AddToBalance
.
Now, we no longer need to define an addToBalance function in ParentAccount:
class ParentAccount : public Account{
virtual int getBalance() {
int result = 0;
for (int i = 0; i < children.size(); i++)
result += children[i]->getBalance();
return result;
}
//no addToBalance function
};
The second most fun part is this: since we have a tree, we can have a generic function defining a visit sequence, which decides in which order to visit the nodes of the tree:
void VisitWithPreOrderTraversal(Account* _node, Visitor* _visitor) {
_node->Accept(_visitor);
for(size_t i = 0; i < _node->children.size(); ++i)
_node->children[i]->Accept(_visitor);
}
int main() {
ParentAccount* root = GetRootOfAccount(...);
AddToBalance* atb = new AddToBalance("pensky_account", 500);
VisitWithPreOrderTraversal(atb, root);
};
The MOST fun part is defining your own Visitor that does much more complex operations (e.g. accumulating the sums of balances of all ChildAccounts only):
class CalculateBalances : public Visitor {
void Visit(ChildAccount* _child) {
balanceSum += _child->getBalance();
}
int CumulativeSum() {return balanceSum; }
int balanceSum;
}