c++里面有一个消息机制,我觉得很方便,在mfc里面以前看到过,但是真正觉得它好用还是在做游戏之后。
    很多人会碰到一个问题,举个例子:游戏里面主角被砍了一刀后(HeroCutEvent),会发生A:gui显示血量减少(Ahandler),B:屏幕闪红(Bhandler),C:主角播放被砍动作(Chandler),D:播放血的特效(Dhandler)。
    英雄被砍事件发生后,如果控制ABCD的程序都不在一个源文件里面,而是分别在四个源文件里面,这下会变的很麻烦,因为要调用其它对象的代码,就要有对它们的引用。如果不想引用呢?而且在调用的时候还可以传参数(比如说ABCDhandler都以此时的血量做参数)。
下面的是我用js写的一个轻型的消息(事件)处理类,当然其它语言也一样可以,思路是一样的
#pragma strict//预处理选项,让编译器强制类型检查 ,C,C++不需要
import System;
import System.Collections.Generic; //要用.net的哈希表 public class MessageManager 
{
    private  static var messages :Hashtable;//哈希表,key是事件类型(DropBloodEvent),value是个数组,数组里面存放处理函数(Ahandler,Bhandler,Chandler,Dhandler)
    private static var initialized:boolean=false;//判断是否初始化
    
    public static function Initialize()
    {
        if(initialized)
        {
            return ; 
        } 
        initialized=true;
        messages=new Hashtable();
    }
    
    public static function AddListener(type:Type,callBack:Function):void //Function类型就是C,C++里面的函数指针,指向一个函数
    {
        Initialize();
        GetListeners(type).Push(callBack);
    }
    
    public static function GetListeners(type:Type):Array//GetListeners就是通过事件类型(HeroCutEvent)找到存储相应处理函数的数组(Array,里面存放ABCDhandler)
    { 
        if(!messages[type.ToString()])
        {
            messages[type.ToString()]=new Array();
            return messages[type.ToString()] as Array;//type.ToString()就是把这个类型读成字符串,作为类的唯一标识(也可用枚举)        }
        else
        {
            var arr:Array= messages[type.ToString()] as Array;
            return arr;
        }    
    }
     
    public static function Dispatch(gameMessage:GameMessage):void
    {       
        for(var i:int=0;i<GetListeners(typeof(gameMessage)).length;++i)
        {
            (GetListeners(typeof(gameMessage))[i] as Function)(gameMessage);
        }        
    }
}public class GameMessage
{
    public function GameMessage()
    {
        MessageManager.Initialize();
    }}public class HeroCutEvent extends GameMessage//主角被砍事件
{
    public var currBlood :float;//这个做参数使用
    public function HeroCutEvent(arCurrBlood:float)
{
     this.currBlood=arCurrBlood;
         super();//就是父类GameMessage的构造函数。
         MessageManager.Dispatch(this);//this就是指自己作为参数
    }
}程序一开始将ABCDhandler加入哈希表。
   在A里面一开始调用MessageManager.AddListener(HeroCutEvent,Ahandler)(Ahandler只对A可见);
   BCD同理。
   程序一开始,由于ABCD调用了AddListener,所以hashtable类型的messages便有了一个key-value对,主角被砍时,只要实例化一个HeroCurEvent的对象就可以了.
New HeroCutEvent(bloodAmt);//新建消息(事件)对象Obj
由于HeroCutEvent的构造函数被调用,所以Dispatch会被调用,参数就是自己这个对象(Obj),Dispatch就是通过这个对象类型来找到ABCDhandler,并一个一个轮流调用,参数就是这个HeroCurEvent对象Obj,ABCDhandler需要的参数(此时主角的血量)被保存在这个对象Obj里面(currBlood一定是public否则ABCDhandler不可读)
以前自学mfc那会搞的不是很明白,但是用了一段事件之后感觉的确很方便,其实还是被高人指点,要感谢我的前一个老板,也算是我的老师。
这里有一个问题就是新建了Obj之后怎么办,就让让它一直在内存里吗?如果是C,C++可以用delete直接处理,如果是java或.NET编译器,那就不要管它了,后台自动垃圾回收。
  用一个全局的哈希表,键就是各个消息类型的唯一标识,值就是一个数组,里面元素是这个消息的处理函数指针。新建一个消息类型对象的时候,构造函数被调用,构造函数会在全局哈希表里面通过自己的唯一标识符索引到处理函数数组,并将这个数组里面的函数循环调用一次。
  真的很方便,我现在做串口通信,串口代码在对象A里面,但是串口要控制的对象是B,A对B没有引用,而且B可能随时被销毁,随时实例化,这样A引用B就更难了。
  之前的程序员就是用引用的方式做的,代码写的很夸张。
  用消息机制之后就很好处理了。
  希望对被同样问题困扰的朋友有所帮助。

解决方案 »

  1.   

    从我上学时使用汇编语言起,我们就使用表驱动机制。在.net,我们从来不忘记“事件驱动”设计思路。依赖倒置是必须的,而不需要事先引用什么。例如我们随便做一个控件(组件),我们根本就不知道将来有几万客户程序使用它,怎么可能引用将来的程序?自然是事件驱动的。在这个方面,我们应该一开始就给初学者启蒙,使得其明白事件驱动的重要性。使用高级语言已经封装好的事件驱动编程机制,而不要自己再发明用过分技术化的东西把这个启蒙说得过于复杂。
      

  2.   

    SP1234说的消息触发和消息处理函数要在同一个类(对象)里面,我写的那个消息机制触发是在A里面,但是消息处理函数可以在对象B里面,区别还是很大的。
      

  3.   

    代码太多就不看了。在OOP中,广义的消息是指如果A程序调用了B程序,我们就说A给B发了一个消息。我们有很多观点可以去看待消息,但是我这里想强调调用这个观点。侠义的消息是指那些将这种调用关系和通知/数据传递机制分开的做法。因此我们得到这么一个基本的认识,消息这个概念本来是不存在的,消息只是一种调用,而这种调用在某些场合不透明,我们必须给一个消息的概念去描述它的实现。为什么不透明呢?举几个例子,比如Windows操作系统的消息(MFC的消息来源于此),因为跨进程是无法直接传递数据和调用的(内存在不同进程中被隔离),同时我们需要处理一些异步收发,这样我们才需要消息。再比如网络通讯更是如此,两台计算机各自有各自的操作系统,各自有各自的程序,当然不能直接调用(我是指像直接在本地代码中执行跳转指令那样调用)。所以,如果你用的环境本身可以把这种调用做的透明,比如你就是写一个程序,在其内部调用,根本没必要多此一举地使用消息。
      

  4.   

    额,当你明白“属性”这个东西原本就是为了做事件驱动编程而出现的
    当你明白事件原本就是可以多播滴,你会发现你做滴其实就是net里原本就有的东西当你读过被博客园们神话化了滴“观测者模式”就会知道如果灰太狼来啦,喜羊羊会砸石头,懒羊羊会哭鼻子,其实也木那么神奇虽然俺实在不想去神话“设计模式”,但是有空去稍微看一眼装饰模式,观察者模式,对你来说也许还不错