How should I create a set of related classes where only some of them support a specific operation?

I am working on slide applications in C ++. Each slide has a collection of slides, which can include elements such as a caption, button, rectangle, etc.

Only some of these elements support padding, while others do not.

What is the best way to implement fill for slide elements in this case? Here are two ways I was thinking about:

  • Create a Fillable interface and implement this interface for slides that support padding while retaining all the properties associated with padding the interface. If you repeat the list of dynamic_cast slide elements in Fillable , and if successful, perform the operation related to filling.

  • Create the fill class. Make the fill pointer a part of the slide element class, assign the fill object to the fill pointer for those objects that support fill, and for the rest they keep the value null. Give a GetFill function that will return fill for elements if it exists, otherwise returns NULL .

What is the best approach for this? I am interested in performance and maintainability.

+6
source share
6 answers

I would make a combination of the two. Create your Fillable interface and get its return type for your GetFill method. This is better than a dynamic approach. Using dynamic casting to a query for an interface requires the actual slide element object to implement the interface if it should support it. However, using an access method, such as GetFill , you can point to a link / pointer to some other object that implements the interface. You can also just return this if the interface is actually implemented by this object. Such flexibility can help avoid class bloating and help create reusable object components that can be used by multiple classes.

Edit: This approach also works well with the null object pattern. Instead of returning a null pointer for objects that do not support Fillable , you can return a simple no-op object that implements the interface. Then you don’t have to worry about always checking for null pointers in client code.

+3
source

Create a base class SlideItem:

 class SlideItem { public: virtual ~SlideItem(); virtual void fill() = 0; }; 

Then do an empty implementation for those that you cannot fill:

 class Button : public SlideItem { public: void fill() { } }; 

And the correct fill implementation for the rest:

 class Rectangle : public SlideItem { public: void fill() { /* ... fill stuff ... */ } }; 

and put all of them in a container. If you want to fill them out, just call everyone ... easy to maintain .. and who cares about performance :)


If you really need fast code, your first solution is definitely good. But if you do it this way, make sure you don’t need to drop it every time you want to fill. Throw them once and place the pointers in a filled container. Then drag this fillable container if you need to fill.

And again, IMHO, you put too much effort into it, without a reasonable increase in productivity. (of course, I do not know your application, this may be justified .. but usually not)

0
source

What you are looking for seems to be close to the Capability Pattern . Your number 2 is close to this pattern. Here is what I will do:

Make a fill class. Make the fill pointer a part of the slide element class, assign a fill object to fill the pointer only for those objects that support fill, since the rest remain zero. Create a GetCapability (Capability.Fill) function that will return padding for elements, if it exists, otherwise returns NULL. If some of your objects already implement the Fillable interface, then you can return the object converted to a Fillable pointer.

0
source

Consider saving Variant elements such as boost::variant .

You can define boost::variant<Fillable*,Item*> (you should use smart pointers if you have ownership), and then have a list of those options for which iteration.

0
source

The answer depends on that.

I see no reason to clutter up your base interface fill/get_fillable_instance/... if not every object needs to handle filling. You can, however, get away with just

 struct slide_object { virtual void fill() {} // default is to do nothing }; 

but it depends on whether you think fill should appear in the abstract class of the slide object. However, this should rarely be done unless the exception is exceptional.

Dynamic casting may be correct if you need to provide two different classes of objects (and no more than two), some of which are fillable, while others have nothing to do with fillability. In this case, it makes sense to have two sub-hierarchies and use dynamic casting where you need it.

I have successfully used this approach in some cases, and it is simple and easy to maintain if the sending logic is not scattered (i.e. there is only one or two places where you dynamically execute).

If you expect that you will have more actions with padding, then dynamic_cast is the wrong choice, as this will lead to

 if (auto* p = dynamic_cast<fillable*>(x)) ... else if (auto* p = dynamic_cast<quxable*>(x)) ... 

what is wrong. If you need it, then create a visitor template.

0
source

I suggest using an interface for shapes, with a method that returns a filler. For instance:

 class IFiller { public: virtual void Fill() = 0; protected: IFiller() {} virtual ~IFiller() {} }; class IShape { public: virtual IFiller* GetFiller() = 0; protected: IShape() {} virtual ~IShape() {} }; class NullFiller : public IFiller { public: void Fill() { /* Do nothing */ } }; class Text : public IShape { public: IFiller* GetFiller() { return new NullFiller(); } }; class Rectangle; class RectangleFiller : public IFiller { public: RectangleFiller(Rectangle* rectangle) { _rectangle = rectangle; } ~RectangleFiller() {} void Fill() { /* Fill rectangle space */ } private: Rectangle* _rectangle; }; class Rectangle : IShape { public: IFiller* GetFiller() { return new RectangleFiller(this); } }; 

I find this method easier to maintain and extend, while it does not cause serious performance issues.

0
source

Source: https://habr.com/ru/post/896796/


All Articles