C ++: applying Composite template

I am trying to apply a Composite template, so I need to create a Leaf class and a Composite class, both inheriting from the same Component class. In order for any of my Components to fulfill their duties, they need to ask for help from one Helper object. We have the following

struct Helper { void provide_help(); }; struct Component { Component(Helper* helper) : m_helper(helper) { } virtual void operation() = 0; // the call_for_help function will be used by subclasses of Component to implement Component::operation() void call_for_help() { m_helper->provide_help(); } private: Helper* m_helper; }; 

And here are two different subclasses of Leaf:

 struct Leaf1 : Component { Leaf1(Helper* helper) : Component(helper) { } void operation() override { call_for_help(); operation1(); } void operation1(); }; struct Leaf2 : Component { Leaf2(Helper* helper) : Component(helper) { } void operation() override { call_for_help(); operation2(); } void operation2(); }; 

So far so good. Now the Composite class gives me sadness. A typical implementation is as follows:

 struct Composite : Component { Composite(Helper* helper) : Component(helper) { } void operation() override { for (auto el : m_children) el->operation(); } private: std::vector<Component*> m_children; }; 

which, having passed through m_children one by one and calling operation , by default calls the auxiliary function many times, although one call is enough for all children. Ideally, if m_children consisted, say, of Leaf1 and a Leaf2 , I would like for the Composite operation to somehow call the helper function only once, and then call Leaf1 :: operation1 (), and then Leaf2 :: work2 ( ) Is there any way to achieve what I need? Alternative designs are welcome. Hope my question makes sense. Thanks in advance!

+4
source share
3 answers

You need a polymorphic operation, but you add extra ability to the method (calling a helper). It’s better to separate these two things.

 struct Component { void call_operation(){ call_for_help(); operation(); } virtual void operation() = 0; void call_for_help(); }; 

Remove call_for_help () from leaf :: operation () (doing operation1, operation2 is redundant, polymorphism) and the rest should work fine.

You can even hide operation () from your public interface, in which case you will need friendship with your Composite.

+1
source

As this could happen at any level, one approach would be to manage that level at the assistant level.

Sketch Approach:

 class Helper { bool composite_help = false; bool help_provided; public: void provide_help() { if ((composite_help && !help_provided) || !composite_help) { //TO DO: provide help help_provided = true; } } void start_composite_help() { composite_help = true; help_provided = false; } void end_composite_help() { composite_help = false; } }; 

The principle is that the help call made by the individual components still works. But when composite calls are for help, you accept preacutions to make sure that the call is made only once:

 void operation() override { m_helper->start_composite_help(); for (auto el : m_children) el->operation(); m_helper->start_composite_help(); } 

As said, this is just a sketch: the code provided as such will not work as soon as you have several levels of composites. Therefore, it is necessary to improve:

  • instead of bool composite_help you will need a counter that increments when you enter a compound operation and decreases when you exit it. In this case, the counter will return to 0 (repeated assistance) only when the last level of compost has completed its work.

  • maybe an assistant performs various operations to provide assistance. Thus, you can also imagine a “transaction identifier” that uniquely identifies a group of related transactions, and you do not manage the counter for the general assistant on the map of active transactions.

  • finally, the beginning / end is not so nice. The RAII helper for the helper can make the whole setup more robust (for example, when an exception disrupts the normal execution flow.)

0
source

I think this problem is best solved with a combination of Composite and Mediator .

Head up! I will show you another version of the mediator template that does not match the canonical version.

It is not the business of your compound structure to know whether an assistant has been called or not. You better do this with some kind of event handler.

Since you only have one helper, you can try like this:

 class Helper { public: void callHelper() { std::cout << "Helper called" << std::endl; } }; class Mediator { private: std::map<std::string, std::vector<Helper>> subscribers; int updateLimit = -1; int currentUpdateCount = 0; void resetUpdateCount() { currentUpdateCount = 0; } public: Mediator(){} void subscribe(std::string evt, Helper helper) { subscribers[evt].push_back(helper); } void update(std::string evt) { for (auto& h: subscribers[evt]) { h.callHelper(); } } void setUpdateLimit(int i) { updateLimit = i; resetUpdateCount(); } void removeUpdateLimit() { updateLimit = -1; resetUpdateCount(); } int getUpdateLimit() { return updateLimit; } void updateLimited(std::string evt) { if (updateLimit < 0 || currentUpdateCount < updateLimit) { update(evt); currentUpdateCount++; } } }; int main(int argc, const char *argv[]) { Mediator m; Helper h1, h2; m.subscribe("bar", h1); m.setUpdateLimit(1); // Will be called only once m.updateLimited("bar"); m.updateLimited("bar"); m.updateLimited("bar"); m.removeUpdateLimit(); return 0; } 

Using it:

 Mediator m; Helper h1, h2; m.subscribe("bar", h1); m.setUpdateLimit(1); // Will be called only once m.updateLimited("bar"); m.updateLimited("bar"); m.updateLimited("bar"); m.removeUpdateLimit(); 

So, here is what you do to integrate this composite structure. Remove the helper from your nodes, add Mediator to the base class:

 struct Component { Component(Mediator& mediator) : m_helper(mediator) { } virtual void operation() = 0; // the call_for_help function will be used by subclasses of Component to implement Component::operation() void notify() { m_mediator->updateFiltered(Component::updateEventName); } static std::string updateEventName; private: Mediator& m_mediator; }; std::string Component::updateEventName = "update.composite"; struct Leaf1 : Component { Leaf1(Helper* helper) : Component(helper) { } void operation() override { notify(); operation1(); } void operation1(); }; 

Using it:

 Mediator m; Helper h; Composite c(m); Leaf1 l1(m), l2(m); c.add(l1); c.add(l2); m.subscribe(Component::updateEventName, h); m.setUpdateLimit(1); // Will be called only once, even if it has childrens c.update(); m.removeUpdateLimit(); 

IMPORTANT: This solution is suboptimal, it has some problems, for example, you need to pass a mediator instance for each node constructor, but this is just the initial idea for you.

Hope this helps!

0
source

All Articles