This is a basic OO design question. I’m writing classes in C++ to represent items in a flow chart according to an input C file that have been parsed.
Simply we have 2 types of items (classes) : FlowChartActionItem and FlowChartConditionItem.
These represent Actions and Decision/Condition elements of a flowchart respectively. And they also represent Statements and If-conditions respectively, that existed in the input C file. Both classes inherit FlowChartItem.
Each sub-classes has a number of pointers to the items that comes after them; yes, we have a graph, with nodes(items) and links(pointers). But the FlowChartActionItem has only one outward pointer while the FlowChartConditionItem has 3 outward pointers (for the then-statements branch, the else-statements branch and a pointer to whatever comes after the both branches of the if-condition.
My problem is writing a neat setter for the outward pointers (nextItems). Take a look at the classes :
class FlowChartItem
{
public:
//I **need** this setter to stay in the parent class FlowChartItem
virtual void SetNextItem(FlowChartItem* nextItem, char index) = NULL;
};
–
class FlowChartActionItem:public FlowChartItem
{
public:
FlowChartItem* nextItem; //Only 1 next item
public:
void SetNextItem(FlowChartItem* nextItem, char index);
};
–
class FlowChartConditionItem: public FlowChartItem
{
public:
FlowChartItem* nextItem;
FlowChartItem* trueBranchItem;
FlowChartItem* falseBranchItem; //we have 3 next items here
public:
void SetNextItem(FlowChartItem* nextItem, char index);
};
I needed a generic setter that doesn’t depend on the number of pointers the sub-class is having.
As you see I’ve used char index to tell the setter which pointer is to be set. But I don’t like this and I need to make things neater. Because code won’t be readable e.g :
item1.setNextItem(item2,1);
we don’t remember what the 1 means? the then-branch ? the else ? ??
The obvious answer is to define an enum in FlowCharItem, but then we’ll have one of two problems :
1- Enum values will be defined Now and will thus be tailored for the current sub-classes FlowChartActioItem and FlowChartConditionItem, so calls to SetNextItem on future sub-classes will have very bad readability. And even worse, they cannot have more than 3 outward pointers!
2- Solve the 1st problem by making developers of the future sub-classes edit the header file of FlowChartItem and add whatever values in the enum ! of course not acceptable!
What solution do I have in order to keep
-good readability
-neat extensibility of my classes ??
This is a form of a common architecture dilemma. Different child classes have a shared behavior that differs slightly and you need to somehow extract the common essence to the base class in a way that makes sense. A trap that you will typically regret is to let the child class functionality bleed into the parent class. For instance I would not recommend a set of potential enum names for types of output connections defined in FlowChartItem. Those names would only make sense in the individual child nodes that use them. It would be similarly bad to complicate each of your sub classes to accommodate the design of their siblings. Above all things, KIS! Keep. It. Simple.
In this case, it feels like you’re overthinking it. Design your parent class around the abstract concept of what it represents and how it will be used by other code, not how it’s inheritors will specialize it.
The name SetNextItem could just be changed to make it more clear what both of the parameters do. It’s only the “next” item in the sense of your entire chart, not in the context of a single FlowChartItem. A flow chart is a directed graph and each node would typically only know about itself and it’s connections. (Also, you’re not writing visual basic, so containers index starting from 0! 🙂 )
Or if you prefer shorter names, then you could set the “N’th” output item:
SetNthOutItem.Since it not valid to set a child using an out-of-range index, then you probably want to have another pure virtual function in FlowChartItem that returns the maximum number of supported children and make SetChildByIndex return a success/failure code (or if you’re one of those people, throw an exception) if the index is out of range.
Now… having written all that, I start to wonder about the code you have that will call this function. Does it really only know about each node as a FlowChartItem, but still needs to set it’s children in a particular order which it doesn’t know the significance of? This might be valid if you have other code which is aware of the real item types and the meaning of their child orderings and that code is providing the item pointers and their index numbers to the code that does the setting. Maybe de-serialization code, but this is not the right way to handle serialization. Is FlowChartItem exposed through a strict API and the chart is built up by code that knows of the different types of flow chart items but does not have access to the actual classes? Maybe valid in that case, but I’m speculating now well beyond the details you’ve provided.
But if this function is only going to be called by code that knows the real item type, has access to the actual class, and knows what the index means, then this probably shouldn’t be in the base class at all.
I can, however, imagine lots of types of code that would need to fetch a FlowChartItem’s children in order, without knowing the significance of that order. Code to draw your flow chart, code to execute your flow-chart, whatever. If you cut your question down for brevity and are also thinking about similar getter method, then the above advice would apply (though you could also consider an iterator pattern).