SDLframework实现之消息循环的封装

来源:互联网 发布:sql备份数据库 编辑:程序博客网 时间:2024/05/16 14:31
        最近一段时间在研究OPENGL,OPENGL本身并不提供窗口系统,在其官网上推荐采用SDL提供窗口和事件循环机制。但是SDL是纯C面向过程的,用它写的代码逻辑容易混乱,于是就萌生了对SDL做部分封装的想法。

        对于学习OPENGL而言,需要SDL提供的功能就相对比较简单。考虑一个最简单的例子:在屏幕上绘制一个三角形,并且能够通过上下左右键在屏幕中移动三角形。从这个简单的例子中可以抽象出两个方法:绘制物体和处理事件。从面向对象的角度,我们可以把这两个方法分别封装成两个接口。
struct Renderer{    virtual void Render() = 0;protected:    Renderer(){}    virtual ~Renderer(){}};struct EventHandler{    virtual bool Handle(SDL_Event& event) = 0;protected:    EventHandler(){}    virtual ~EventHandler(){}};
        在这个例子中,三角形(Triangle)代表了一个具体想表达的对象。从MVC模式的角度看,Renderer相当于一个View用来呈现物体,EventHandler对应于Controller响应用户的请求并发送消息给Model和View,而三角形数据就是具体的一个Model了,它可以表示等边三角形、直角三角形等。至于,如何用C++表示M、V和C的关系,可以视具体情况而定。这个例子中,我们可以让Triangle同时实现Renderer和EventHandler接口。
class Triangle : public Renderer, public EventHandler{public:    Triangle();    virtual ~Triangle();public:    virtual void Render();    virtual bool HandleEvent(SDL_Event& );};
        当然,如果不移动三角形,那么只要实现Renderer接口就OK了。这就是为什么把图形绘制和事件处理这两个方法分别放在两个接口而不是一个接口中的原因了,这也体现了面向对象中的某某解除耦合的思想。
        上面的这个简单的例子描述了我希望具体的应用在我封装的SDLframework下是如何编写的。“依赖接口而不是实现”的面向对象设计原则,告诉我接下来的SDLframwork实现只能依赖于Renderer和EventHandler这两个接口,跟Triange类没什么关系了。因为Triangle代表具体实现,它可以是一个triangle也可以是一个rectangle或是circle之类的东西。
        在实现SDLFramework之前,先回顾下曾经接触过的framework。最早接触到的framework当然是Microsoft的MFC,MFC为开发者做了很多东西,虽然已经很久很久没有用MFC了,但还可隐约记得:如果需要在View上绘制某些东西的话,需要重载CView类的OnDraw方法,然后在这个方法内绘制;如果要打开或者关闭一个文档的话,就要重载CDocument类的OnOpenDocument方法(具体的方法名已经不记得了,这拼写可能是不对的);如果要自定义消息的话,就要在好几个地方添加关于消息的描述,这样framework才能知道有这个消息,并且在适当的时候调用它。MFCFramework对开发者隐藏了消息循环机制,并把开发者自己的类抽象得分为了Document和View两大类。除了MFC,最近接触到的一个framework便是android了。Android似乎为开发者做了更多的工作。android的五大组件(Activity、Service、BroadcastReceivers、Intent和ContentProvider)几乎定义所有应用的模式,这就是抽象的威力。开发者只需要扩展Activity就能够生成一个应用程序,Activity中又声明了很多虚函数,来让开发者去填写它们以实现应用程序的差异化。框架中,多态的这种特性被高涣堂称为“无为之用”,我很喜欢这种说法,这种说法让人有本地化的感觉,原来这种思想不是外来的而是几千年前老子就有了的,蛮亲切的。
        简言之,MFC和Android这两个framework的共同作用是为了减轻开发者的负荷。它替开发者做了每个app要不停重复的工作。比如在MFC和Android中,开发者看不到
int main(int argc, char* argv[]){    return0;}
        这个所有程序的入口点不存在了吗?当然不是的,它是被framework托管了,开发者只要更加专注于自己的工作就可以了。这好像又隐约体现了面向对象设计的单一职责原则。ok,现在已经清楚framework的真正定义了,那就开始我的SDLFramework吧。想一想用SDL撰写程序的时候,我们经常必须要完成哪些东西呢,这些东西就是framework需要代为处理的哦。第一个想到的就是消息循环,每次都要写个while循环来处理消息累不累?给它封装起来吧。
class Viewer{public:    voidRun(){        while(IsDone()){            if(!HandleEvents())                Quit();             Frame();   // to render triangle.         }   }   void Quit(){       m_done = false;   }   bool HandleEvents();};
        撰写程序时,我们需要分辨清楚什么是变化的什么是不变的。硬编码不变部分,封装变化部分。在上例中,while循环是保持不变的,而循环内的待处理事件是变化的,比如,三角形需要处理上下左右按键来移动,而圆形只需要处理加减按键来缩放,不必处理左右上下按键消息。将变化部分封装在HandleEvents方法内部,并暴露方法来让框架使用者添加、移除需要处理的事件。在Viewer类中添加两个添加删除事件的方法。      
class Viewer{public:    void AddEventHandler(EventHandler* eventHandler);    bool RemoveEventHandler(EventHandler* eventHandler);};
        这样在方法HandleEvents()内部就可以依次遍历通过AddEventHandler方法添加的事件来处理开发者指定的消息了。
        同样地,对应Renderer接口我们也采用相同的方法。
class Viewer{public:     void AddRenderer(Renderer* renderer);     void Frame(); // render contents in renderer list.};
开发者通过AddRenderer接口向Viewer添加Renderer,并通过Frame()渲染Renderer中的内容。
        此外,不考虑多线程的情况,Viewer类可以看成一个单例模式。
class Viewer{private:    Viewer();public:    static Viewer& GetInst();    ~Viewer();};
现在,看看在这个framework下该怎么实现一个可以渲染三角形并通过左右上下键可以移动的具体应用了。
int main(int argc, char* argv){    Viewer& viewer = Viewer::GetInst();    Triangle tri;    viewer.AddEventHandler(&tri);    viewer.AddRenderer(&tri);    viewer.Run();    return0;}
哦,代码简洁了很多。不过它还存在许多问题,比如内存如何管理、多线程情况下如何处理、同时存在多个Model时该如何传递具体事件到Model上等等。


原创粉丝点击