Adding class functionality through composition

Suppose we have an abstract Element class from which the Triangle and Quadrilateral classes are Quadrilateral .

Suppose these classes are used in conjunction with interpolation methods that depend on the shape of an element. So, basically we create an abstract class InterpolationElement , from which we derive InterpolationTriangle and InterpolationQuadrilateral .

Then, to include the interpolation functionality in the Triangle and Quadrilateral , we add a const-reference data element in the Element class of type InterpolationElement , that is:

 class Element { public: Element(const InterpolationElement& interp); const InterpolationElement& getInterpolation() const; private: const InterpolationElement& interpolation; }; 

Then we create a method (as described by Scott Meyers, Effective C ++) that initializes the local static object of the InterpolationTriangle class as

 const InterpolationTriangle& getInterpolationTriangle() { static InterpolationTriangle interpolationTriangle; return interpolationTriangle; } 

Thus, the Triangle class can be constructed as follows:

 class Triangle : public Element { public: Triangle() : Element( getInterpolationTriangle() ) {} }; 

Here is my question: is this approach correct for including interpolation methods in my Element class? Is it used in professional scenarios?

I could directly implement all the interpolation methods of the Element class (as pure virtual) and override them in the derived Triangle and Quadrilateral . However, this approach seems cumbersome to me, since every time I need to improve or implement new interpolation functions, I would have to do this on these classes. Moreover, classes are becoming more and more (many methods) using this approach.

I would like to hear from you some tips and comments

Thanks in advance.


Additional Information:

 class InterpolationElement { public: InterpolationElement(); virtual double interpolationMethod1(...) = 0; : virtual double interpolationMethodN(...) = 0; } class InterpolationTriangle : public InterpolationElement { public: InterpolationTriangle () {} virtual double interpolationMethod1(...) { // interpolation for triangle } : virtual double interpolationMethodN(...) { // interpolation for triangle } } class InterpolationQuadrilateral : public InterpolationElement { public: InterpolationTriangle () {} virtual double interpolationMethod1(...) { // interpolation for quadrilateral} : virtual double interpolationMethod1(...) { // interpolation for quadrilateral} } 
+4
source share
5 answers

Classes are used in conjunction with interpolation methods. Why should these methods be in the same object? Singleton looks very problematic.

 class Element { public: virtual double interpolationMethod1(...) = 0; : virtual double interpolationMethodN(...) = 0; }; class Triangle : public Element { public: virtual double interpolationMethod1(...) { // interpolation for triangle } : virtual double interpolationMethodN(...) { // interpolation for triangle } } 

Also welcome to SO!

+1
source

This is reminiscent of the question I answered here . The same idea of ​​separating data containers and strategies.

+1
source

There is one small problem in your proposal: you added an interpolation matching method to your base class, and you changed the constructor ...

So, first of all, if you want to do it this way, here is how you should do it:

 class Element { public: private: // similar signature to a `clone` method virtual InterpolationElement* interpolation() const = 0; }; class Triangle { public: private: virtual InterpolationTriangle* interpolation() const { return new InterpolationTriangle(); } }; 

There are 2 advantages here:

  • You no longer need to change the constructor of each of the derived objects
  • The strategy object is no longer const , which allows it to maintain state during the calculation ... as a reference to the current object that is being interpolated.

However, this still requires changing the Element class and each of its derived classes. Does this bother you;)?

Well, it's time (this time) to invoke the design template: Visitor .

This is a little different from the idea of ​​a strategy, relying on double dispatch to work properly. However, this allows you to set up the Element ONCE hierarchy (using the accept method), and then add as many operations as you like. And that’s great.

+1
source

You can always do a little work with templates. First we have a class.

 class Element { public: virtual void calculate() const = 0; }; 

... but then we also have a class in the middle of the hierarchy, which is actually a template. A template cannot be a top-level class, because templates with different parameters are different classes. The idea is that we provide an interpolation class as a type parameter for an element.

 template <typename Interpolation> class Element_Impl : public Element { protected: Interpolation m_interpolation; }; 

And interpolation classes. Please note that they are not brothers and sisters, because they do not need it.

 class InterpolationTriangle { public: double interpolate(double a, double b) const { std::cout << "interpolation triangle" << std::endl; } }; class InterpolationQuadrilateral { public: double interpolate(double a, double b) const { std::cout << "interpolation quadrilateral" << std::endl; } }; 

And finally, the real elements and a small basic procedure.

 class Triangle : public Element_Impl<InterpolationTriangle> { public: void calculate() const { m_interpolation.interpolate(1.0, 2.0); } }; class Quadrilateral : public Element_Impl<InterpolationQuadrilateral> { public: void calculate() const { m_interpolation.interpolate(2.0, 3.0); } }; int main() { const Element &a = Triangle(); const Element &b = Quadrilateral(); a.calculate(); b.calculate(); } 

Summary:

  • You can easily switch the interpolation class for each item if necessary.
  • there is no vtable dual access (first to compute an element, and then to intpolate InterpolationElement methods), as in the Matthieu example. Each element knows at compile time the interpolation class that it uses.
  • Element_Impl is an ugly bit, but it saves us from copypasta. You can expand it even further by wrapping the interpolation method
  • http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

One way is to use static methods and define a wrapper in Element_Impl - still in only one place.

 class Element { public: virtual void calculate() const = 0; }; template <typename Interpolation> class Element_Impl : public Element { protected: void interpolate(double, double) const { Interpolation::interpolate(1, 1); } }; class InterpolationTriangle { public: static double interpolate(double a, double b) { std::cout << "interpolation triangle" << std::endl; } }; class InterpolationQuadrilateral { public: static double interpolate(double a, double b) { std::cout << "interpolation quadrilateral" << std::endl; } }; class Triangle : public Element_Impl<InterpolationTriangle> { public: void calculate() const { interpolate(1.0, 2.0); } }; class Quadrilateral : public Element_Impl<InterpolationQuadrilateral> { public: void calculate() const { interpolate(2.0, 3.0); } }; int main() { const Element &a = Triangle(); const Element &b = Quadrilateral(); a.calculate(); b.calculate(); } 
+1
source

First, the GoF Design Pattern Visitor comes to my mind.

From what I understand about your problem, this template is thought out to precisely solve this problem.

Each Visitor object defines an interpolation method or algorithm applied to your object.

Thus, the Element class does not grow with each new functional function. Once upon a time, a visitor’s template allows enriching functionality without touching the definition of a base class.

+1
source

All Articles