Skip to main content
1 of 2
doubleYou
  • 2.8k
  • 1
  • 13
  • 26

I would like to expand on Emerson Cardoso's "other suggestion" because I believe it to be the correct approach in the general case - though you may of course find other solutions better suited to any particular problem.

The Problem

In your example, the AbstractShape class has a getType() method that basically identifies the concrete type. This is generally a sign that you don't have a good abstraction. The whole point of abstracting, after all, is not having to care about the details of the concrete type.

Also, in case you're not familiar with it, you should read up on the Open/Closed Principle. It's often explained with a shapes example, so you'll feel right at home.

Useful Abstractions

I assume you have introduced the AbstractShape because you found it useful for something. Most likely, some part of your application needs to know the perimeter of shapes, regardless of the what the shape is.

This is the place where abstraction makes sense. Because this module does not concern itself with concrete shapes, it can depend on AbstractShape only. For the same reason, it does not need the getType() method - so you should get rid of it.

Other parts of the application will only work with a particular kind of shape, e.g. Rectangle. Those areas will not benefit from an AbstractShape class, so you shouldn't use it there. In order to pass only the correct shape to these parts, you need to store concrete shapes separately. (You may store them as AbstractShape additionally, or combine them on the fly).

Minimizing Concrete Usage

There is no way around it: you need the concrete types in some places - at the very least during construction. However, it's sometimes best to keep the use of concrete types limited to a few well-defined areas. These separate areas have the sole purpose of dealing with the different types - while all application logic is kept out of them.

How do you achieve this? Usually, by introducing more abstractions - which may or may not mirror the existing abstractions. For example, your GUI doesn't really need to know what kind of shape it is dealing with. It just needs to know that there is an area on the screen where the user can edit a shape.

So you define an abstract ShapeEditView for which you have RectangleEditView and CircleEditView implementations that hold the actual text boxes for width/height or radius.

In a first step, you could create a RectangleEditView whenever you create a Rectangle and then put it into a std::map<AbstractShape*, AbstractShapeView*>. If you would rather create the views as you need them, you might do the following instead:

std::map<AbstractShape*, std::function<AbstractShapeView*()>> viewFactories;
// ...
auto rect = new Rectangle();
auto viewFactory = [rect]() { return new RectangleEditView(rect); }
viewFactories[rect] = viewFactory;

Either way, the code outside of this creation logic will not have to deal with concrete shapes. As part of the destruction of a shape, you need to remove the factory, obviously. Of course, this example is over-simplified, but I hope the idea is clear.

Choosing the right Option

In very simple applications, you might find that a dirty (casting) solution just gives you the most bang for your buck.

Explicitly maintaining separate lists for each concrete type is probably the way to go if your application mainly deals with concrete shapes, but has some parts that are universal. Here, it makes sense to abstract only so far as the common functionality requires it.

Going all the way generally pays if you have a lot of logic that operates on shapes, and the exact kind of shape really is a detail to your application.

doubleYou
  • 2.8k
  • 1
  • 13
  • 26