|
-
February 18th, 2004, 08:19 AM
#1
OO visitor design problem
I have an abstract class WaveBase, and derived concrete classes TimeWave and FrequencyWave, which hold time or frequency waveform data.
I'm using MFC doc/view. The document holds a WaveBase* called pWave.
I need to draw graphs of the data, but the different types of data are rendered differently.
WHere there is a TimeWave already displayed, on redraw nothing should happen. Where pWave has been transformed from a TimeWave to a FrequencyWave, on redraw a new graph should be displayed and the axes recalculated and autoscaled to the new data.
My problem is finding a suitable OO design to acheive this, without using RTTI/dynamic casts.
So far, I have an abstract class GraphBase, and derived concrete classes TimeGraph and FrequencyGraph. The view class holds a GraphBase* called pGraph.
I am using the visitor pattern to recover the lost type info for pWave, so that the appropriate graph type can be created, and drawn appropriately using polymorphism (not shown here).
My current implementation for CView::OnDraw is:
GraphFactory gf; //WaveBase visitor
pWave->Accept(gf);
where
Code:
class GraphFactory{
void visitTimeWave(TimeWave* ) // delete pGraph, create new TimeGraph, autoscale axes
void VisitFrequencyGraph(FrequencyWave* ) // delete pGraph, create new FreqGraph, autoscale axes
};
However, the creation (or not) of a new graph type should also depend on the previous graph type. ie if a TImeWave is being displayed on a TImeGraph, on redraw nothing should happen (as it is currently, the axes are redrawn each time because a new graph is created on each redraw - I do not want this to happen). But if a the TimeWave has been transformed to a FrequencyWave then it should be redrawn as a frequency graph, and axes be autoscaled to the new data.
Note, there is an Axes object which holds the axes limits.
I can see a way to do it using visitors for the graph AND the wave, but this becomes VERY clumsy. I would have to have a TimeGraphFactory and a FreqGraphFactory.
Really I want to be able to have a function which behaves as shown below, only in my case the type info has been lost:
UpdateGraph(TimeWave, TimeGraph) // do nothing
UpdateGraph(TimeWave, FreqGraph) // new TimeGraph, autoscale axes etc
UpdateGraph(FreqWave, TimeGraph) // new FreqGraph, autoscale axes etc
UpdateGraph(FreqWave, FreqGraph) // do nothing
I'm sure there must be a 'standard' solution to this problem, and I would be extremely grateful if anyone could point me in the right direction.
As I have said before, I can do it 'neatly' creating a new graph using the visitor pattern and type of the wave to be displayed, but I ALSO need to use the previous type of the graph to decide whether to make a new object or not.
Thanks
Jon
-
February 19th, 2004, 06:13 AM
#2
I hope that this is what you are looking for. I just wrote the code right off my head without testing. This may not be the best solution.
I added an extra parameter into the Visitor's Accept() so that I can determine the new type is the same as the current graph object. If the current graph is the same as the new type, nothing happens. Otherwise, the GraphFactory deletes pGraph and creates a different graph.
Code:
class WaveBase;
class GraphFactory
{
public:
enum
{
TimeType,
FreqType
};
void visitWave(TimeWave* pB, int newType)
{
if( ! pB->isTime(newType) )
{
// newType is different.
delete pGraph;
pGraph = create(newType);
}
}
void visitWave(FrequencyWave* pB, int newType)
{
if( ! pB->isFreq(newType) )
{
// newType is different.
delete pGraph;
pGraph = create(newType);
}
}
WaveBase* create(int newType)
{
switch(newType)
{
case TimeType:
return new TimeWave;
break;
case FreqType:
return new FrequencyWave;
break;
default:
return NULL;
}
}
};
class TimeWave : public WaveBase
{
public:
....
Accept(GraphFactory& gf, int newType)
{
gf.visitWave(this, newType);
}
bool isTime(int newType)
{
return newType == GraphFactory::TimeType;
}
};
class FrequencyWave : public WaveBase
{
public:
....
Accept(GraphFactory& gf, int newType)
{
gf.visitWave(this, newType);
}
bool isFreq(int newType)
{
return newType == GraphFactory::FreqType;
}
};
// In some function.
...
GraphFactory gf;
pWaveBase->Accept(gf, newType);
-
February 19th, 2004, 08:45 PM
#3
On second thought, it seems that for your case, the Visitor pattern doesn't help much. The Visitor pattern is best suited for extending functionalities and using double dispatch to determine which function to call depending on both the visitor type and element type.
Probably, a simpler solution is to keep an int member variable in the Factory class, for tracking the type of the previously created Graph object. When the newer requested type is different, you can destroy the pGraph and create a different Graph object.
-
February 20th, 2004, 06:43 AM
#4
Thanks for your reply.
I was really hoping for a more elegant OO solution, rather than just having a type variable and a switch statement - I may as well use RTTI, but I'd rather not do this.
I have actually implemented it using 2 visitors - one to visit the Wave* to resolve the actual type, and one to visit the old Graph*. It does work, but it is clumsy - I have to have separate CreateTimeGraph and CreateFreqGraph objects. But as you said, it is not really what the visitor pattern is intended to solve.
Jon
Posting Permissions
- You may not post new threads
- You may not post replies
- You may not post attachments
- You may not edit your posts
-
Forum Rules
|
Click Here to Expand Forum to Full Width
|