代码改变世界

零零碎碎 -- 继承与代理

2013-03-06 15:34  robturtle  阅读(260)  评论(0编辑  收藏  举报

继上次尝试用 boost::any 容纳窗口部件失败后,使用了类的继承特性实现,这是昨天初始版本的一个快照:

ifndef  WINDOWCOMPONENT_HPP
#define  WINDOWCOMPONENT_HPP
#include  <string>
#include  <memory>
#include  <list>
using std::string;
using std::shared_ptr;
using std::list;
#include    "cppcommon.hpp"
using namespace cliout;

namespace cvoo { // OpenCV with OO window classes

// A abstract of Window's components (e.g., trackbar, scrollbar)
// Window's handler will show it or not according to needShowed()
class WindowComponent {
public:
    // notify window's handler create its resources
    virtual void Create(int flag = 0) = 0;

    // Visibility operations (implemented already)
    void Visible(bool stat)
    { showed = stat; }

    bool needShowed() const 
    { return showed; }

    void FlipVisible()
    { showed = !showed; }

    WindowComponent() {}
    virtual ~WindowComponent() {}

protected:
    // It's weired if you create a component to a window
    // and it doesn't show up by default
    bool showed = true;
};

// Named Window is identified by its name
class NamedWindowComponent: public WindowComponent {
public:
    // if window with this name existed, create the component on it
    // Otherwise, it may create a new window (at OpenCV2/highgui)
    // or DO NOTHING (other name-based GUI)
    virtual void Create(const string & windowName, int flag = 0) = 0;
    const string & GetName() const {}

    NamedWindowComponent() {}
    virtual ~NamedWindowComponent() {}
};

} // namespace

#endif  /*WINDOWCOMPONENT_HPP*/

然后窗口就可以用一个容器储存和管理其上的部件。这个时候问题来了,目前的设计没有办法支持对组件的分组操作。比如在播放器加入高斯滤波功能,并提供3个滚动条调整滤波参数,那么很自然地,用户一定会希望打开/关闭滤波功能时,这三个滚动条也同时显示或消失。为了实现这个功能,我想到了《C++沉思录》里的“代理”模式:用被代理类实现“节点”语义,用代理类实现“边”语义。大致构思如下:

class ComponentHandler;

class WindowComponent {
public:
    WindowComponent() {}
    virtual ~WindowComponent() {}
    
    void foo()
    {
        foo_self();
    }

protected:
    virtual void foo_self() = 0;
};

class ComponentGroup: public WindowComponent {
public:
    ComponentGroup()
    // intialize sub_components_
    {}
    virtual ~ComponentGroup() {}

    void foo()
    {
        foo_self();
        sub_components_->foo();
    }

protected:
    shared_ptr<ComponentHandler>
      sub_components_;
};

class ComponentHandler {
public:
    using item_t       = shared_ptr<WindowComponent>;
    using container_t  = std::container<item_t>;
    // for Example: std::container = std::map<string, item_t>;

    void foo()
    {
        for (auto sp : components_)
            sp->foo();   // if container == map, than it will be sp.second->foo()
    }

protected:
    container_t components_;
}

如此这般,只要在窗口内声明一个 ComponentHandler, 就可以创建出无限复杂的部件包含关系了。

 

第一次尝试失败了,因为我想把没有 ID 参数的 WindowComponent 和以字符串名字为 ID 的 NamedWindowComponent 统一在一起。这在写它们的句柄类的时候遇到了麻烦,我无法用统一的容器装载它们。然后我又一次尝试使用 boost::any ,很可惜的,std::map<boost::any, content_t> 却无法通过编译,似乎any不是一个可以比较的类。

第二次尝试化繁为简,因为这个类是用在 cv::namedWindow 的包装上的,所以干脆只考虑以字符串为ID这一种情况。然后,很快就写出了如下的内容:

WindowComponent.hpp

#ifndef  WINDOWCOMPONENT_HPP
#define  WINDOWCOMPONENT_HPP
#include  <string>
#include  <memory>
#include  <map>
using std::string;
using std::shared_ptr;
using std::make_shared;

namespace cvoo {

class NamedWindowComponent {
protected:
    // You created it, you showed it.
    bool showed = true;

public:
    NamedWindowComponent() {}
    virtual ~NamedWindowComponent() {}

    virtual void Create(const string & windowName, int flag = 0)
    { CreateSelf(windowName, flag); }

    virtual const string & GetName() const = 0;

    // Visibility control
    void Visible(bool stat)
    { showed = stat; }

    void FlipVisibility()
    { showed = !showed; }
    bool needShowed()
    { return showed; }
protected:
    virtual void CreateSelf(const string & windowName,
                            int            flag = 0) = 0;
};

class NamedWindowHandler {
public:
    using item_t = shared_ptr<NamedWindowComponent>;
    using container_t = std::map<string, item_t>;
protected:
    container_t components_;

public:
    void AddComponent(item_t comp)
    { components_[comp->GetName()] = comp; }

    virtual void Create(const string & windowName, int flag = 0)
    {
        if (components_.size() == 0) return;
        for (auto pr : components_)
            pr.second->Create(windowName, flag);
    }
};

class NamedComponentGroup: public NamedWindowComponent {
public:
    using item_t = shared_ptr<NamedWindowComponent>;
protected:
    shared_ptr<NamedWindowHandler> sub_components_;

public:
    NamedComponentGroup()
    : sub_components_(make_shared<NamedWindowHandler>())
    {}
    virtual ~NamedComponentGroup() {}

    virtual void Create(const string & windowName, int flag = 0)
    {
        CreateSelf(windowName, flag);
        sub_components_->Create(windowName, flag);
    }

    void AddComponent(item_t comp)
    { sub_components_->AddComponent(comp); }
};

} // namespace

#endif  /*WINDOWCOMPONENT_HPP*/

然后用上面的声明包装 cv::namedWindow 和 cv::trackbar:

NamedWindow.hpp

#ifndef  NAMEDWINDOW_HPP
#define  NAMEDWINDOW_HPP
#include    "opencv2/highgui/highgui.hpp"
#include    "WindowComponent.hpp"

namespace cvoo {

class NamedWindow: public NamedComponentGroup {
protected:
    string win_name_;
    int    flag_;

public:
    NamedWindow(const string & windowName = "", 
                int            flag       = 0)  
    : win_name_(windowName)
    , flag_(flag)
    {}  

    virtual ~NamedWindow()
    { cv::destroyWindow(win_name_); }

    const string & GetName() const
    { return win_name_; }

    // Special methods
    inline void Show()
    {   
        Visible(true);
        Create(win_name_, flag_);
    }
   
    inline void Redraw()
    { Create(win_name_, flag_); }

    inline void Hide()
    {
        Visible(false);
        cv::destroyWindow(win_name_);
    }

    void Rename(const string & newName)
    {
        if (newName != win_name_) {
            cv::destroyWindow(win_name_);
            win_name_ = newName;
            Create(win_name_, flag_);
        }
    }

    void SetFlags(int flag)
    { flag_ = flag; }

    // Image Displayer
    void Display(const cv::Mat & img)
    { cv::imshow(win_name_, img); }

protected:
    void CreateSelf(const string & windowName, int flag = 0)
    {
        cv::destroyWindow(win_name_);
        win_name_ = windowName;
        flag_     = flag;

        if (needShowed()) {
            cv::namedWindow(windowName, flag);
        }
    }
};

} // namespace

#endif  /*NAMEDWINDOW_HPP*/

Trackbar.hpp

#ifndef  TRACKBAR_HPP
#define  TRACKBAR_HPP
#include    "WindowComponent.hpp"
#include    "opencv2/highgui/highgui.hpp"

namespace cvoo {

class Trackbar: public NamedWindowComponent {
public:
    using callback_t      = void (*)(int, void*);
    using callback_data_t = void *;
private:
    string          name_;
    string          win_name_cache_ = "";
    int *           ptr_pos_;
    int             max_pos_;
    callback_t      callback_;
    callback_data_t data_;

public:
    Trackbar(const string &  trackbarName,
             int *           pPos,
             int             maxPos,
             callback_t      callback = 0,
             callback_data_t callback_data = 0)
    : name_(trackbarName)
    , ptr_pos_(pPos)
    , max_pos_(maxPos)
    , callback_(callback)
    , data_(callback_data)
    {}

    ~Trackbar() {} // no derived

    const string & GetName() const
    { return name_; }

    // Special methods
    void SetCallback(callback_t callback, callback_data_t data)
    {
        callback_ = callback;
        data_     = data;

        if (!win_name_cache_.empty()) {
            // because flag can only influence sub components
            // so it's no need to store or pass flags
            CreateSelf(win_name_cache_);
        }
    }

    // Other Accessors
    const string & GetWindowName() const
    { return win_name_cache_; }

    int            GetPos()        const
    { return *ptr_pos_; }

    int            GetMaxPos()     const
    { return max_pos_; }

    callback_t     GetCallback()   const
    { return callback_; }

    callback_data_t GetCallbackData() const
    { return data_; }

protected:
    void CreateSelf(const string & windowName, int flag = 0)
    {
        win_name_cache_ = windowName;

        if (needShowed()) {
            cv::createTrackbar(name_, windowName,
                               ptr_pos_,
                               max_pos_,
                               callback_,
                               data_);
        }

        if (callback_)
            callback_(GetPos(), data_);
    }
};

} // namespace

#endif  /*TRACKBAR_HPP*/

最后赞美一下 Boost 的 unit test framework, 上面有好些缺陷都是在做单元测试时找到的,比如在重新创建滚动条后立刻调用回调函数等等。