I have a design question which I have not been able to find a satisfactory answer for by searching.
Essentially, I have a class - Base - that contains a vector of pointers to another class - Node. However, I would like to be able extend both the Base and Node class. This will involve adding new variables and functions to the classes derived from Node, and utilising these additional functions in classes derived from Base.
I can think of a few ways to accomplish this, but am unsure which is the best from a design point of view, as they each have their flaws. It has led me to think that perhaps there is a different design altogether which can accomplish what I need more elegantly.
Here is some example code illustrating my problem:
class Node
{
protected:
int value;
public:
Node(int v): value(v)
{ }
void GetValue()
{ return value; }
virtual void Print()
{
std::cout << "Value is: " << value << std::endl;
}
};
class BetterNode: public Node
{
protected:
double another_value;
public:
BetterNode(int v, double v2): Node(v), another_value(v2)
{ }
void GetBetterValue()
{ return another_value; }
virtual void Print()
{
Node::Print();
std::cout << "Better Value is: " << another_value << std::endl;
}
};
class Base
{
protected:
std::vector< Node* > some_nodes;
virtual void MakeNodes()
{
for(int i = 0; i < 10; i++)
some_nodes.push_back( new Node(i) );
}
public:
Base()
{
Initialise();
}
void PrintNodes()
{
for(int i = 0; i < some_nodes.size(); i++)
{
std::cout << "Node " << i << ":" << std::endl;
some_nodes[i]->Print();
}
}
int AddValues()
{
int temp = 0;
for(int i = 0; i < some_nodes.size(); i++)
temp += some_nodes[i]->GetValue();
return temp;
}
};
class BetterBase
{
virtual void MakeNodes()
{
for(int i = 0; i < 10; i++)
some_nodes.push_back( new BetterNode(i, i/2) );
}
public:
BetterBase()
{
Initialise();
}
double AddBetterValues()
{
double temp = 0;
for(int i = 0; i < some_nodes.size(); i++)
temp += some_nodes[i]->GetBetterValue(); // THE PROBLEM LIES HERE.
return temp;
}
};
int main(void)
{
Base * b = new Base;
Base * better_b = new BetterBase;
// Output details about Nodes:
b->PrintNodes();
// Virtual function in BetterNode takes over and outputs additional Detail:
better_b->PrintNodes();
// Lets find out what the sum of the node values from b are:
int b_values = b->AddValues();
std::cout << b_values << std::endl;
// We *know* that better_b contains additional information. What is it?
double better_values = better_b->AddBetterValues();
std::cout << better_values << std::endl;
// But ofcourse, this compiles with an error, because AddBetterValues does not exist in Base class..
return 0;
}
So, My derived BetterNode class is naturally going to extend the base Node class, and will need new values and consequently new functions to access them, that are unknown to the base Node class. However, it will share the common base properties.
In addition, the derived BetterBase class wishes to implement these BetterNodes and utilise their additional functionality in performing what it does. It will fill its some_nodes vector with pointers to BetterNodes. It can therefore use all of its parent Base class functions, as the BetterNode is derived from the Node.
I have tried to illustrate this problem in the above code. I do not need to reimplement my Print function, as classes derived from Node can virtually overload the Print function, which saves me effort. This is good. However, My derived BetterBase class wants to make use of the extended functionality of BetterNode, in this example by implementing AddBetterValues(). It obviously cannot, as the vector some_nodes is pointers to the base Node class, which does not know about this function.
So, How can I pull this off?
I have thought of a few ways, but am unsure which is best:
1. Make the Base class a template class, whereby I select which type of Node the vector some_nodes will hold.
example:
template<class T = Node> Base
{
protected:
vector<T*> some_nodes;
//...other code.
};
class BetterBase: public Base<BetterNode>
{
// now, AddBetterValues would work, as the vector is not of base class Node, but of BetterNode.
}
The problem here is that it allows for mistakes to be made. What if I derive from the Base class like this:
class AnotherBase: public Base<NonNodeClass>
Templating allows me to fill that vector with anything, not just classes derived from Node, which could screw things up. As such, this seems like a sloppy and potentially error-prone workaround.
2. Whenever I add functions to classes derived from Node, I can also prototype/define these functions as virtual in the base Node class. Now, the base class knows about additional functions, and there are no issues,
However, this seems like bad design practise, and people who derive from my classes should not need to go back and add relevant code into my base Node class. In addition, bulking out the base Node class with functions it does not itself need is just stupid.
3. Casting in derived classes. If, knowing that I will be using BetterNodes in the BetterBase class, I simply cast from Node* to BetterNode* when necessary, I would provide the additional BetterBase functions access to the required additional BetterNode functions. The downside to this approach is the requirement for an increasing amount of casting as the class derived from Node grows more complex, which seems sloppy. Also, people tend to say that this is a sign of a design flaw. As it seems like the simplest way to overcome my problem, does this mean that there is a better way I could structure my code?
So, which method is best? Or, do I need to think about redesigning my code entirely?
Thanks in advance for your help!
James,