I have the following C++ code (simplified version):
class Shape
{
bool isCircle = false;
bool isSquare = false;
}
class Circle : public Shape
{
// some special members/methods
}
class Square : public Shape
{
// some special members/methods
}
class CAD
{
virtual DrawCircle(Circle * circle) = 0;
}
class SWX : public CAD
{
virtual DrawCircle(Circle * circle){// do some stuff that draws circle on SWX system}
}
class PRO : public CAD
{
virtual DrawCircle(Circle * circle){// do some stuff that draws circle on PRO system}
}
int main()
{
Circle * circle = new Circle();
circle->isCircle = true;
Square * sq = new Square;
sq->isSquare = true;
vector<Shape*> shapes;
shapes.push_back(circle);
shapes.push_back(sq);
SWX * swx = new SWX();
for( int i = 0 ; i < shapes.size() ; ++i )
{
if( shapes[i]->isCircle )
{
SWX->DrawCircle((Circle*)(shapes[i]));
}
else if( shapes[i]->isSquare )
{
SWX->DrawSquare((Square*)(shapes[i]));
}
}
I wish to remove the need for if…else (if at all possible within the constraints stated below).
My constraints right now are:
- The CAD and derived classes are ginormous classes with various external dependencies.
- The CAD classes cannot be merged with the Shape and derived classes (that would have been ideal, since then I can use polymorphism to solve my problem), since other projects/classes depend on the Shape classes and cannot depend on the CAD classes.
- There are more than a dozen Shape-derived classes with a half dozen CAD-derived classes and this if…else is happening in numerous locations – so it would help if any solution is simple to understand (easier to convince my teammates to change legacy code).
Any suggestions/comments/solution you have would be most welcome.
The standard solution for this problem, especially given your constraints regarding dependencies, is to use the Visitor Pattern.
Here’s how Visitor Pattern would work in your case:
ShapeVisitorclass. It has an abstractVisitmethod for each concrete subclass of Shape. eg:Visit(Circle*),Visit(Square*), etc.AcceptVisitor(ShapeVisitor*)method.AcceptVisitoras just callingvisitor->Visit(this)CADclass is a (or has-a, up to you) aShapeVisitor. TheVisitmethods do the appropriate drawing for the specific type ofShape. No conditional or casting required.Here’s a modified version of your code that uses Visitor Pattern in a pretty low-impact way:
In this code I’ve opted for making
CADactually a subclass ofShapeVisitor. Also, since you’ve already got virtual methods inCADto do the drawing, I implemented theVisitmethods there (once), rather than once in each subclass. Once you switch clients over to the usingAcceptVisitorinstead of calling the Draw* methods directly you could make those methods protected, and then eventually move the implementation of theVisitmethods down to the subclasses (that is: refactor to remove the extra level of indirection caused by havingVisit(Foo*)callDrawFoo(Foo*)).