C++: Applying the Composite pattern

时间:2015-08-14 22:44:33

标签: c++ design-patterns class-design composite

I am trying to apply the Composite pattern, 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 perform their duty they need to ask help from a single 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 Leaf subclasses:

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 is giving me grief. The 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 by going through the m_children one by one and calling operation on each essentially calls the helper function multiple times, even though one call is enough for all children. Ideally, if the m_children consisted, say, of a Leaf1 and a Leaf2, I would like somehow the Composite operation to call the helper function only once and then call in succession Leaf1::operation1() and then Leaf2::operation2(). Is there any way to achieve what I need? Alternative designs are welcome. I hope my question makes sense. Thanks in advance!

2 个答案:

答案 0 :(得分:0)

因为它可能发生在任何级别,一种方法可能是在帮助程序级别处理此问题。

该方法的草图将是:

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; 
    } 
};

原则是各个组件执行的求助呼叫与以前一样有效。但是当复合请求帮助时,您需要进行预抽真空以确保仅执行一次调用:

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

如上所述,这只是一个草图:如果您有多个级别的复合材料,那么提供的代码将无法正常工作。所以这需要改进:

  • 而不是bool composite_help你需要一个计数器,它在输入复合操作时递增,在退出时递减。在这种情况下,只有当最后一级composte完成其工作时,计数器才会返回0(重新启用帮助)。

  • 可能是助手执行不同的操作来提供帮助。因此,您还可以设想具有唯一标识一组相关操作的“事务ID”,并且在活动事务的映射中管理计数器而不是整体帮助程序。

  • 最后,开始/结束并不是那么好。帮助程序的RAII帮助程序可以使整个设置更加健壮(例如,当异常中断正常执行流程时。)

答案 1 :(得分:0)

我认为使用Composite和Mediator的组合可以更好地解决这个问题。

  

抬起头来!我将向您展示不同版本的中介模式,这与规范版本不同。

您的复合结构的业务不是要知道是否调用了帮助程序。你最好使用某种事件处理程序来做到这一点。

由于你只有一个助手,你可以尝试这样:

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;
}

使用它:

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();

因此,您可以采用以下方法将此功能集成到您的复合结构中。从您的节点中删除帮助程序,将Mediator添加到基类:

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();
};

使用它:

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();

重要提示:此解决方案不是最理想的,它存在一些问题,例如您必须将介体实例传递给每个节点构造函数,但这对您来说只是个不切实际的想法

希望它有所帮助!