一个基于observer模式的游戏事件分发系统

c++观察者模式observer 概念 Observer模式是行为模式之一,它的作用是当一个对象的状态发生变化时,能够自动通知其他关联对象,自动刷新对象状态。 Observer模式提供给关联对象一种同步通信的手段,使某个对象与依赖它的其他对象之间保持状态同步。 角色和职责 Subject(被观察者) 被观察的对象。当需要被观察的状态发生变化时,需要通知队列中所有观察者对象。Subject需要维持(添加,删除,通知)一个观察者对象的队列列表。 ConcreteSubject 被观察者的具体实现。包含一些基本的属性状态及其他操作。 Obse 阅读详情

-潘宏

-2012年11月

-本人水平有限,疏忽错误在所难免,还请各位高手不吝赐教

-email: popyy@netease.com

-weibo.com/panhong101


在游戏引擎以及产品开发中,程序员需要让大量的系统模块相互进行通信。在大多数时候,模块的数量是巨大的。在最坏的情况下,N个相互通信的模块需要N*(N-1)/2种依赖关系。就算是最简单的游戏产品,N的数目也是可观的。



如果设计者放任这种相互依赖的模块关系于不顾,整个系统将高度耦合,代码牵一发而动全身。对一个模块的修改,将导致多个模块无法工作,必须进行相应调整。这将导致程序员的焦躁情绪,从而很容易让局面难以控制。 此外,还需要忍受长时间的重新编译、长时间加班修补bug。这些是OOP设计模式(指GoF的经典著作所描述的)所要努力避免的。


一个设计合理、高效的系统,需要对模块进行解耦——使得模块间的依赖关系减少。同时开发者将获得更好的代码维护性和扩展性,并节省大量的编译时间。本文描述了一种基于observer模式的事件分发系统(Event Dispatch System,EDS),用来进行游戏模块的相互通信。在一般的情况下,N个模块的相互通讯只需要N种依赖关系(借助于EDS)。


我们通过描述基本知识来逐步构成我们对系统的深层理解。


Observer模式


Observer模式也叫publish/subscribe模式或listener模式,它实际上是一种利用callback方法进行通信的机制。依照GoF的描述,它定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都得到通知并自动更新。如下图所示,



我把上面的图帮你解释一下(如果你是该模式的专家,可以忽略解释):一个Subject关联任意多个Observer,这在多态下也一样,一个子类ConcreteSubject也可以关联任意多个Observer。而Observer也自成体系,但Subject体系一般只操作基类Observer。开发者可以通过Subject::Attach和Subject::Detach将目标Observer绑定到它所关心的Subject身上,当Subject产生任何变化需要通知Observer的时候,Subject::Notify方法会通知所有被绑定的Observer。同时,得到通知的Observer都可以访问到它们所关心的Subject,得到它们相应的状态。


通俗地说,一个模块A如果想在模块B进入某种状态的时候,做一些事情,我们需要知道B进入状态的时机,并在该时机通知A。这引出我们所说的通知模型概念,在软件开发中,有两种通知模型被使用:Poll和Callback。


Poll通知模型


一种最简单的通知方法叫作轮询(poll)——模块A需要在每个CPU周期中查询B是否进入了该状态——这其实是一种主动的自我通知。比如:

    class B  
    {  
    public:  
        //...  
        bool isFinished() const { return m_isFinished; }  
        void cycle()  
         {   
            //...  
        }  
    private:  
        // ...  
        bool m_isFinished;  
      
    };  
      
    class A  
    {  
    public:  
        void poll( B& b )  
        {  
            if( b.isFinished() )  
            {  
                // Do something  
            }  
        }  
    };  
      
      
      
    int main()  
    {  
        A a;  
        B b;  
      
        for(;;)  
        {  
      
            b.cycle();  
            a.poll( b );  
        // ...  
    
   }  
        return 0;  
    }  


A类的class object a,在每一个主循环周期中通过A::poll对B类的class object b进行状态查询,当发现B::isFinished返回true,也就是条件满足,则进行相应的操作。


这就是状态轮询惯用法——简单、直接,也有些野蛮!它有两个致命的缺点:


1)轮询代码是查询方主动调用,查询方不知道对方何时完成,因此如果需要对查询结果进行快速反应,需要在每个周期中进行查询调用,大量的CPU周期被浪费在无效的查询指令中。

2)当模块的数量提高,这种轮询方式将产生代码急剧膨胀,每两个有依赖关系的模块,都需要在周期中进行相互查询。这样的代码将难以甚至无法维护。


因此,轮询一般只在规模不大的几个模块间调用,并且模块状态不能太多。


Callback通知模型


和轮询相对的,是让B主动的通知A,A被动等待B的完成并通知,在这段等待时间中A可以继续做其他的事情,不用浪费CPU周期无谓地查询目标状态,这叫作callback——回调,也是observer模式的实现策略。很明显,callback克服了poll模型的两个致命缺点,但代码相对复杂一些。在C++ OOP中,我们一般有三种方式来实现callback。


1 使用静态成员函数

    class A  
    {  
    public:  
        static void onBFinished( void* param )  
        {  
            A* _this = static_cast<A*>(param);  
            // ... Do something using _this  
        }  
        void cycle()  
        {  
            // Do something...  
        }  
    };  
      
      
    typedef void (*FPtrOnBFinished)( void* param );  
    class B  
    {  
    public:  
        void setCallback( FPtrOnBFinished callback, void* param ) { m_callback = callback;                      m_param = param; }  
        void cycle()  
        {  
            // Finished in some cases  
            m_callback(m_param); // Notify the subscriber  
        }  
    private:  
      
    FPtrOnBFinished   m_callback;  
    void* m_param;  
    };  
      
    int main()  
    {  
      
        A a;  
        B b;  
        b.setCallback( A::onBFinished, &a );  
      
        for(;;)  
        {  
            a.cycle();  
            b.cycle();  
        }  
      
        return 0;  
    }  


可以看到B存储了A的静态方法地址以及A class object指针,该指针将作为参数传递给A的静态方法,以决定到底是谁——哪个对象对该事件感兴趣,并通知它。


该指针实际上充当了非静态成员函数中this指针的作用——我们给static方法显示地放置了一个“this指针”——实参param,编译器不需要、也不可能干预static成员函数的this指针安插。这可以通过A::onBFinished看出,实参param被转换为A*,然后针对该指针(也就是subscriber本身——希望得到通知的观察者)进行相应操作。


很容易想到,class B的subscriber可以是多个,这也是observer的设计初衷。你可以扩展B的类接口来增加、减少subscriber和它们相应的callback。将它们妥善保存在一个容器里面,并在B::cycle中通过迭代通知每一个subscriber。


回调方法中的形参类型void*保证了subscriber对于publisher是类型无关的,只要它能够在相应的callback方法中被正确的cast成真实的类型,就可以了。这提供了很大的灵活性,是这种回调方法的中心思想。另一方面,这种方式也会导致潜在的类型安全问题——这要求客户程序员必须保证subscriber类型的一致性。如果把一个错误的类型对象作为param给了publisher,程序仍可以经过编译,但在运行期subscriber的callback方法中的cast会以失败告终,这通常是危险的。


2 使用指向成员函数的指针

    class A  
    {  
    public:  
        void onBFinished();  
        void cycle()  
        {  
            // Do something...  
        }  
    };  
      
    typedef void (A::*onBFinished)();  
    class B  
    {  
    public:  
        void setCallback( onBFinished callback, A* a ) { m_callback = callback; m_a = a; }  
        void cycle()  
        {  
            // ...  
            // Finished in some cases  
            (m_a->*m_callback)();  
        }  
      
    private:  
      
        onBFinished m_callback;  
        A* m_a;  
    };  
      
    int main()  
    {  
        A a;  
        B b;  
        b.setCallback( &A::onBFinished, &a );  
        for(;;)  
        {  
            a.cycle();  
            b.cycle();  
        }  
      
        return 0;
}  



static member function被替换成了指向成员函数的指针。


这对类型安全性起到了一定促进作用:在B::cycle中对指向成员函数的指针进行调用的时候,编译器会进行type-checking,从而将错误的类型匹配拦截在编译期。


另一方面,由于必须指定指向成员函数指针的类作用域,比如A::,B需要知道所有subscriber的类型。这在一定程度上增加了类的耦合性:系统每增加一个subscriber类,就需要在publisher类中定义相应的callback typedef,这将带来维护开销,并增加重编译时间。而这个问题在上面的static方法中是不存在的。


下面的方法则解决了类型安全和类耦合两个问题(这也是observer模式的一种正规实现方式)。


3 使用Listener类


建立一个监听器基类,使用多态方法进行通知。

    class Listener  
    {  
    public:  
        virtual ~Listener() {}  
        virtual void onSomeEventReceive() = 0;  
    };  
      
    class A : public Listener  
    {  
    public:  
        virtual void onSomeEventReceive()  
        {  
            //...Do something  
        }  
    };  
      
    class B  
    {  
    public:  
        void setListener( Listener* l ) { m_listener = l; }  
        void cycle()  
        {  
            // Finished in some cases  
            m_listener->onSomeEventReceive();  
        }  
      
    private:  
        Listener* m_listener;  
      
    };  
      
    int main()  
    {  
        A a;  
        B b;  
        b.setListener( &a );  
      
        for(;;)  
        {  
            b->cycle();  
        }  
      
        return 0;  
    }  




这种方法中,B不需要知道listener的具体类,它只需处理listener抽象类。系统通过多态来高效地复用基类代码。这很好地解决了上述的类型安全和类耦合两个问题:


a)至少在B的层面上,B处理的必须是listener类,B只调用它的方法,编译器保证了这一点。

b)增加任何一个新的listener子类,B都不需要关心,它只处理listener类。B的代码不需要重新编译。


该方法有缺点吗?当然。缺点主要在于它使用了C++的virtual机制,这是C++的性能落后于C的主要开销之一——通过增加间阶层来提高设计抽象性而带来的开销。但,这样的性能损失相对于该结构所带来的设计优势而言,是可以接受甚至忽略的。


通过以上三个方法,我们看到了每种回调方式的优缺点。后面我们会使用listener方式来构建我们的事件系统,因为对于我们的系统来说,它更易于维护、扩展,更符合OOP精神。


游戏事件系统


现在我们回到游戏消息系统主题中来。首先我们已经决定使用回调方式来进行消息通知,更进一步来说,我们打算采用listener方案。接下来我们要考虑我们如何定义模块结构。


为了避免文章开头所说的N个模块的最差依赖关系N*(N-1)/2,同时为了保证每两个模块之间都可以进行通信,我们的事件系统将采用一个中介模块,这实际上是一个mediator模式(如果对mediator模式感兴趣,请参考GoF的书)的简化版本。整个系统结构如图所示:




A到H每个模块都和mediator通信,Mediator会把相应的消息发给相应的模块。这样,N个模块的依赖关系只有N个——每个模块只和mediator相依赖。


为了设计出这个系统,我们开始结合observer模式思考,在listener例子里面,我们把subscriber作为一个listener指针存在于publisher中,我们说这个subscriber对这个publisher要发布的内容感兴趣——它等待着publisher的一个特定的条件产生,从而通知subscriber,它会做它该做的事情。然而,在那个例子中,两个模块是直接依赖的,并不适用于我们现在设计的这个系统,我们要实现的系统将提供一种“多路分派”——可以有多个subscriber接收同一个事件。在该系统中,需要把“特定条件的产生”变成一个实体用来在模块和mediator之间传递来进行间接依赖,一个很自然的方案就是command模式( 如果对command模式感兴趣,请参考GoF的书),它封装着通信接收者需要完成的工作信息,将工作延迟到真正的目标。


这里,我不想牵扯太多的模式,因为这样会导致大量的题外细节。因此,我们暂时不使用标准的command模式,改用一个简单的事件结构体:


struct GameEvent

{

int m_eventID;

int m_params[6];

};


一个整型事件ID和若干个自定义整型参数。对,没错,只有整型参数。那我们如何传递其他类型的东西呢?比如一个字符串什么的。一般来说,需要采用辅助数据结构,这和你的游戏引擎结构有很大的关系。比如用一个字符串池,用m_param来传递池索引等等类似的方案。


一个game event的数据类似这样:


#define GAME_EVENT_ID_CLICK_BUTTON   1000

GameEvent event;

event.m_eventID = GAME_EVENT_ID_CLICK_BUTTON;

event.m_params[0] = 15;

event.m_params[1] = 2666;


上面的event可能在游戏中解释为:一个按下按钮的事件,按下的按钮索引为15,这将产生ID为2666的脚本执行。具体的事件参数由接收它的模块来进行解释。


有了这样一个结构,我们就可以把它和对它感兴趣的模块进行关联。比如对GAME_EVENT_ID_CLICK_BUTTON事件感兴趣的是模块A、C、E、F。则在mediator中可以建立一个关联数组保存下面的关联:


{GAME_EVENT_ID_CLICK_BUTTON, {A, C, E, F}}


每当一个publisher发送了一个GAME_EVENT_ID_CLICK_BUTTON事件给mediator,它都会把该消息转发给{A, C, E, F}模块组。这样,在系统运行的某个时刻,mediator将保存一个映射表,描述了注册的游戏事件以及要响应的模块,比如:


{GAME_EVENT_ID_1, {A, B}}

{GAME_EVENT_ID_5, {C}}

{GAME_EVENT_ID_6, {A, M}}

{GAME_EVENT_ID_11, {F, K, M, S}}

{GAME_EVENT_ID_99, {D, B, P, T, X, Z}}

...


系统结构如下所示:




对该系统的实现


接下来我打算给该系统一个简单实现。给模块设计的类为:


中介模块Mediator: EventDispatcher

通信模块A-H: EventListener

    EventDispatcher* dispatcher;  
    class EventDispatcher  
    {  
    public:  
        EventDispatcher() { dispatcher = this; }  
        void dispatchEvent( const GameEvent& event )  
        {  
            ListenerGroup::iterator it = m_listeners.find( event.m_eventID );  
            if( it != m_listeners.end() )  
            {  
                list< EventListener* >& listeners = it->second;   
                for( list< EventListener* >::iterator it2 = listeners.begin(); it2 != listeners.end(); ++it2 )  
                {  
                    it2->handleEvent( event );  
                }  
            }  
        }  
      
      
    private:  
        typedef map< int, list< EventListener* > > ListenerGroup;  
      
    private:  
        ListenerGroup m_listeners;  
      
        friend class EventListener;  
    };  
      
    class EventListener  
    {  
    public:  
        virtual ~EventListener() {}  
      
        void addListener( int eventID, EventListener* listener )  
        {  
            EventDispatcher::ListenerGroup::iterator it = dispatcher->m_listeners.find( eventID );  
            if( it == dispatcher->m_listeners.end() )  
            {  
                list<EventListener*> l;  
                l.insert( l.begin, listener );  
                dispatcher->m_listeners.insert( make_pair(eventID, l ) );  
            }  
            else  
            {  
      
                list<EventListener>::iterator it2;  
                for( it2 = it->second.begin(); it2 != it->second.end(); ++it2 )  
                {  
                    if( *it2 == listener )  
                    continue;  
                }  
      
                it->second.insert( it->second.begin(), listener );  
            }  
        }  
      
        void removeListener( int eventID, EventListener* listener )  
        {  
            EventDispatcher::ListenerGroup::iterator it = dispatcher->m_listeners.find( eventID );  
            if( it != dispatcher->m_listeners.end() )  
            {  
                for( list<EventListener*>::iterator it2 = it->second.begin(); it2 != it->second.end(); ++it2 )  
                {  
                    if( *it2 == listener )  
                    {  
                        it->second.erase(it2);  
                        break;  
                    }  
                }  
                if( it->second.empty() )  
                    dispatch->m_listeners.erase(it);  
            }  
        }  
      
        virtual void handleEvent( const GameEvent& event ) = 0;  
      
    private:  
      
    };  
      
    class A : public EventListener  
    {  
    public:  
        A() { addListener( 5, this ); }  
        virtual ~A() { removeListener( 5, this ); }  
        virtual void handleEvent( const GameEvent& event )  
        {  
            assert( event.m_eventID == 5 );  
            assert( event.m_params[0] = 1 );  
            assert( event.m_params[1] = 2 );  
        }  
    };  
      
    int main()  
    {  
        EventDispatcher eventDispatcher;  
        A a;  
        GameEvent event;  
        event.m_eventID = 5;  
        event.m_params[0] = 1;  
        event.m_params[1] = 2;  
        eventDispatcher.dispatchEvent( event );  
        return 0;  
    }  

(为了阅读方便,我把类的方法实现直接和声明放到了一起,在实际的开发中,除了需要inline的方法,应该将实现和声明分离)


EventDispatch类即mediator保存了事件-模块映射表m_listeners。EventDispatch::dispatchEvent方法用来处理相应事件。


EventListener类是一个监听器基类,任何一个想成为subscriber的模块都该继承于它。两个方法


EventListener::addListener

EventListener::removeListener


用来将subscriber增加到事件-模块映射表中,或将模块从表中删除。


EventListener::handleEvent方法是一个纯虚方法,subscriber子类应该实现该方法来处理它感兴趣的事件。比如class A就是一个subscriber,它将自己和ID为5的事件绑定——表明它对该事件感兴趣。当在main中通过EventDispatcher发送了一个5号事件的时候,A::handleEvent就会被callback并将5号事件发送给它进行处理。这里的模块传递为:


main模块 -> event dispatcher(mediator) -> class A object



改进方向


以上实现只是一个基本结构——它完成基本的功能,但要让该事件系统达到工程级别并使用,还需要进一步的努力。以下几个改进留给感兴趣的读者当作练习完成。


1 将事件分发方法改为异步方式


这个改进是必须的!问题在于我们在EventDispatcher::dispatchEvent中使用了STL的容器迭代器进行事件分发,当一个事件被EventListener的子类模块通过EventListener::handleEvent进行处理的时候,可能会产生另一个event(比如在A::handleEvent里面再用EventDispatch分发一个事件),这会导致EventDispatcher::dispatchEvent中的迭代器被重复使用,STL不允许这种情况产生,程序崩溃!


解决方案是在EventDispatcher::dispatchEvent中只把event存入一个pending容器中而不调用EventListener::handleEvent。然后给EventDispatcher安插一个flushEvents的接口,在里面统一调用EventListener::handleEvent来处理pending events然后清除掉它们。


2 将EventDispatcher设计为singleton模式


EventDispatcher应该只有一个,并应该被各个通信模块方便地访问。这意味着把它写成一个singleton模式是理所当然。就这么做吧!(关于singleton模式,请参考GoF的书)


3 增加通道


为了在不同的游戏界面中采用不同的事件表布局,可以给监听器组增加不同的通道,简单来说就是把EventDispatcher::m_listeners变成一个数组


EventDispatcher::m_listeners[N]


然后增加一个变量


EventDispatcher::m_curChannel


来表示当前使用的是数组的哪个维度——哪个通道。这样,我们就能够保留不同的界面事件布局而不用频繁的重新初始化。


4 增加Listener的优先级属性


可以给listener增加优先级,这样在EventDispatcher::dispatchEvent中对listener进行调用的时候将按照listener的优先级调用。这种特性可以用来实现一个很酷的功能——消息独占模块:把一个模块的优先级调成最大,然后给该listener增加一个“事件处理后中断后续listener处理”标志,可以让dispatcher在EventDispatcher::dispatchEvent的循环中只调用该优先级最大的listener的事件处理方法,然后终止掉循环。一个标准的模态对话框就可以用该特性实现。


总结

以上我们循序渐进地实现了一个基本的事件分发系统,它实现了对模块的解耦——通过一个中介模块event dispatcher。在一个普通的游戏中,事件的数量是很大的,但该方案保证了增加游戏事件的便利性。它保证了最小的耦合度——事件是完全独立的。理论上,发送者和接收者彼此都不需要了解彼此,这更像是一个广播系统,收听者可以随时打开或关闭收音机,广播电台根本不知道谁在收听广播。

这个系统已经在我们自己的多个项目中被使用,它确实提供给开发者一定的便利性和健壮性。希望你能够从这个系统中有所收获。

原文地址:http://blog.youkuaiyun.com/popy007/article/details/8242787

【愚公系列】二十三种设计模式(十九)-观察者模式(Observer Pattern) 设计模式(Design Pattern)是软件开发领域的宝贵经验,是多人反复借鉴和广泛应用的代码设计指导。它们是一系列经过分类和归纳的代码组织方法,旨在实现可重用性、可维护性和可理解性。使用设计模式,我们能够编写高质量的代码,使其更易于他人理解,并提供了代码可靠性的保证。毫无疑问,设计模式对个人、团队和整个系统都带来了显著的益处。它们将代码开发提升到工程化水平,为软件工程构建提供了坚实的基础,就如同大厦的一块块精巧的砖石一样。在项目中明智地应用设计模式可以完美地解决各种复杂问题。 阅读详情

相关推荐

eventdispatcher:C ++ Event Dispatcher支持TCP,UDP,管道,文件系统,TLS,优先级,OnOff,线程,永久连接等

介绍 eventdispatcher是我们的快照! 网络库。 它使我们能够在任意数量的计算机之间的所有服务之间进行通信。 它是我们的libsnapwebsites的第一部分,内容如下: snap_communictor.cpp/.h snap_communictor_dispatcher.cpp/.h tcp_client_server.cpp/.h udp_client_server.cpp/.h 现在,这些文件全部分解为单独的文件(在撰写本文时,其中有84个文件),并可以在contrib文件夹中的库支持下使用。 特征 Event Dispatcher具有许多功能,这是此库中的主要功能: TCP-普通和加密(TLS) UDP-直接和广播 RPC-基于文本的消息通信 调度程序-自动调度RPC消息 事件-支持许多基于文件描述符的事件 接受套接字(服务器) 套接字读取 套接字

【C++】详解 eventpp 事件调度程序和回调列表库

eventpp 是一个用于回调、事件调度程序和事件队列的C++事件库。使用 eventpp,您可以轻松实现信号和插槽机制、发布者和订阅者模式或观察者模式。wqking/eventpp:事件调度程序和C++回调列表 (github.com)

Fanjufei的博客 2618

别再买错磁铁了!手把手教你为TLE5012B/AS5047等磁编码器选配和安装径向充磁磁铁

本文详细解析了为TLE5012B/AS5047等磁编码器选配和安装径向充磁磁铁的关键要点。从充磁方向的物理本质到供应商沟通技巧,再到安装工艺的黄金法则,帮助工程师避免常见错误,确保磁编码器稳定工作。特别强调了径向充磁的重要性及验证方法,并提供了尺寸选择、温度特性和抗干扰设计的实用建议。

weixin_26750481的博客 315

U3D游戏开发框架(八)——消息系统

一:目的 为什么要使用消息系统呢,假如我们的游戏中现在有一个敌人和一个玩家,当敌人击中玩家后玩家需要执行自身的扣血操作,那么就需要在敌人类中引用玩家脚本,这样就造成了代码的耦合,消息系统在开发中是一个非常重要的系统,他的作用就是解耦合 Unity提供的消息机制SendMessage可以非常方便我们的脚本开发,但它实现的是一种伪监听者模式,利用的是反射机制,比较低效,几乎不会有人在项目中使用Unity内置的SendMessage,一般都自己去实现一套消息系统 二:解决的问题及优点 ——解耦合 三:使用

L的博客 4941

多摄像头同步技术:帧同步信号在Sensor与解串器间的传递机制

本文深入探讨了多摄像头系统中帧同步信号的两种核心传递机制:由解串器统一发号施令或由主Sensor作为“带头大哥”。文章对比了两种方案在同步精度、硬件复杂度、成本及适用场景上的差异,并提供了实战配置要点与调试心法,为车载环视、ADAS及工业检测等应用提供关键的技术选型与实施指导。

weixin_29193359的博客 434

【IC设计实战】去耦电容decap在电源完整性中的关键作用与选型策略

本文深入探讨了去耦电容(decap)在IC设计电源完整性中的核心作用。文章分析了decap如何通过提供本地电荷缓冲来抑制电源噪声与电压波动,并详细阐述了其选型与布局策略,包括MIM、MOM、MOS电容的特性对比,以及如何权衡峰值抑制与建立速度的矛盾,为设计稳定可靠的芯片电源系统提供实践指导。

weixin_29233857的博客 247

观察者模式(Observer)

和其它形式的回调函数(callback)类似,Observer模式也允许你通过挂钩程序(hookpoint)改变代码。不同之处在于,从本质上说,Observer模式是完全动态的。它经常被用于需要根据其它对象的状态变化来改变自身(状态)的场合,而且它还经常是事件管理系统(eventmanagement)的基本组成部分。无论什么时候,当你需要用完全动态的方式分离呼叫源和被呼叫代码的时候,(

timequark的专栏 1929

【软碟通 | UltraISO】刻录光盘教程(制作U盘启动盘)

前言 今天老师说办公室的电脑有点卡,让我给重新安装一个 Windows 7 系统。于是就用软碟通(UltraISO)在 U 盘上刻录了一个 Windows 7 镜像,然后给老师重装了系统。顺便把制作 U 盘启动盘的教程也分享给大家。 软件 UltraISO v9.7.1.3519:点击跳转(密码:dd2k) 教程 将 U 盘插到电脑上,然后鼠标右击选择格式化(制作启动盘必须格式化) 点击文件-打开 选择下载好的系统镜像(如:Windows 7) 点击启动-写入硬盘映像 选择硬盘

XavierJ的博客 2万+

unity前端架构之事件系统完整代码实现

事件资源类是真正处理事件的内部类,它有三个重要的接口TriggerEvent 触发相应事件订阅Subscribe相应事件取消订阅Unsubscribe相应事件if (!return;ULog.Warn("重复注册事件 {0} {1}", type, handler);return;= null)订阅事件接口取消订阅接口立即触发接口发送事件。

lejian的专栏 1339

BCIC IV 2a数据集实战:从MATLAB导入到EEG信号分析全流程

本文提供了BCIC IV 2a数据集从MATLAB导入到EEG信号分析的完整实战流程。详细解析了数据集结构,区分了EEG与EOG信号,并演示了信号可视化、基础预处理(滤波、分段、基线校正)等关键步骤,帮助脑机接口研究者快速上手处理这一经典运动想象数据集。

weixin_29047919的博客 468

TikTok Shop API密钥获取与PHP实战对接指南

本文详细介绍了TikTok Shop API密钥的获取流程与PHP实战对接方法。从开发者后台创建应用、获取核心的API密钥开始,逐步解析OAuth 2.0授权机制,并提供了完整的PHP封装类示例,涵盖签名生成、API调用、令牌刷新及错误处理,帮助开发者快速实现与TikTok Shop的数据对接与自动化操作。

weixin_29300241的博客 400

事件Observer模式

在认识委托和事件之后,可以进阶一下一些设计模式了,这篇主要描述的是关于事件处理机制中的基础,一种叫做“观察者模式”的设计模式:ObServe设计模式,这种设计模式是针对面向对象语言,对象间的一种一对多的依赖关系,以便于主干的状态发生变化时,其他依赖于主干的依赖象也会受到影响,自动告知并更新;这种设计模式也是一种松耦合的设计模式; 再深入的描述一下Observe模式中主要存在那些成员,之间有什么样...

老夫的少女心 278

MATLAB使用贝叶斯网络bnt工具箱:使用手册详解

MATLAB使用贝叶斯bnt网络工具箱:使用手册详解

WW、forever的博客 2675

游戏设计模式与实现之:观察者模式 与 游戏事件系统 (非回调函数版本)

GoF说明: 在对象之间建立一个一对多的链接方法,当一个对象发生改变时,其他关联的对象都会自动接收到通知 模式说明: 游戏中当某个公共的数据改变之后,通常很多需要显示的地方都要做出相应的改变。如果没有设计模式,我们是不是需要一个个的去通知,并且还需要做一下空判断,看所有需要通知的对象是不是已经在场景中了,这是不好的,数据发生改变,正确的做法时我们已经通知已经在场景中并且在关注这个数据的对象就可以了。这就是观察者模式的核心,为公共数据建立一个主题,所有想观察这个主题的对象把自己注册进这个主题。但主题发生改变时

u014078887的博客 533

Claude Code Cli 源码泄露事件全解析:从51万行代码中拆解AI Agent的记忆护城河

2026年3月,Anthropic的AI编程助手Claude Code意外泄露51万行核心代码,暴露了其多Agent架构、六级权限验证系统及创新的记忆机制。记忆系统采用四层设计:用户自定义框架、自动记忆提取、智能上下文压缩和纯文件存储。最独特的是"自动做梦"功能,AI会定期整理记忆,类似人类睡眠记忆巩固。代码还揭示未发布功能如电子宠物系统,但也暴露权限系统漏洞和代码质量问题。这次泄露展现了AI助手核心技术的复杂性,特别是其记忆系统如何通过透明可编辑的设计建立用户信任。

program哲学 468

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

观察者模式(Observer):一件事发生,关心它的人自动收到通知

/ 观察者接口// 主题接口。

本博客聚焦游戏开发技巧、创业实战心得及大学热门课程干货。这里既有技术深度,也有思维广度,无论你是开发者、创业者还是高校学子,都能在这里找到适合自己的成长之路! 1250

ssm框架搭建

这个是一个在idea上成功跑起来的ssm框架项目,保存在这里方便以后使用直接下载

游戏编程模式——观察者模式(Observer)

观察者模式(Observer Pattern)是应用最广泛的GoF模式,本文将介绍一些观察者模式在游戏中的应用。

m0_62210253的博客 937

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

复分析 阿尔福斯(Ahlfors) 第三版答案 英文

答案分为十章,有部分习题没有答案,全英文复分析阿尔福斯(Ahlfors) 第三版答案

游戏编程模式——观察者模式

游戏编程模式——观察者模式 文章目录游戏编程模式——观察者模式概述类图代码实现被观察对象——Subject物理系统类 Physics观察者抽象类 Observer成就Achievements实体类(Entity)监听事件 (Event)main方法 概述 ​ 观察者模式是我们在游戏中常用到的一种设计模式,比方说在UI系统中,我们消耗了金币,要更新当前UI界面上所有和金币相关的元素,如果UI上只有一个修改项还好,但当修改的元素过多时,一个个修改就会很麻烦,也容易遗漏,而且代码耦合性也很高,通常我们可以用观察

qq_40874760的博客 389

行为设计模式之Observer(观察者)

摘要: Observer(观察者)模式定义对象间一对多的依赖关系,当被观察者状态改变时,自动通知所有观察者。适用于RBAC权限同步、消息队列(如RabbitMQ)、游戏状态更新、监控报警等场景。结构包含Subject(目标)、Observer(观察者)及具体实现类,通过注册/通知机制实现解耦。典型应用包括:GUI事件处理、MVC数据同步、发布订阅系统、库存监控、游戏开发等。代码示例展示了Java实现,其中ConcreteSubject维护观察者列表并在状态变更时触发通知,ConcreteObserver更新

qq_45938780的博客 1403

详解Java设计模式之观察者模式(Observer Pattern)

观察者模式又叫做发布-订阅(Publish/Subscribe)模式,模型-视图( Model/View)模式、源-监听器( Source/Listener)模式或从属者(Dependents)模式。观察者模式是一种对象行为型模式。

Elegance never goes out of fashion 3627

unity里很重要的设计模式 - 观察者模式(Observer)

unity游戏开发里最常用的是 singleton 和 observer 设计模式,下面这个链接 中的讲解非常好,是关于观察者模式的,而且还能学到C#中委托,事件的用法,接口,抽象类的知识。 http://www.cnblogs.com/wangjq/archive/2012/07/12/2587966.html //////////////////////// 以防万一,...

lynon的专栏 1万+
上一篇: 深入探索透视纹理映射(下)
下一篇: 一个基于组合模式的游戏地图系统
cbbbc
博客等级 码龄15年 609粉丝 37原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值