/////////////////////////////////////////////////////////////////// // Using Command Objects to Implement Callbacks // // // // Jim Fawcett, 4/13/95 // /////////////////////////////////////////////////////////////////// /* Design Notes: ------------- Command objects are a generalization of callbacks. A Command is essentially a wrapper for function pointers and any other infor- mation necessary to invoke some target function. Commands are especially useful for invoking member functions of some class because two pointers, not one, are needed. One points to a specific object's state (where the this pointer points) and the other points to the member function's code. The example below defines a protocol command class and derives from it: - a command class for global functions of signature void fun() - another command class for signature void X::fun(). It is necessary to derive a separate class for each signature you want to callback. +---------+ +---------+ | command |<----------------| invoker | +----+----+ +---------+ | | defined by your library - - - - - - - - -+- - - - - - - - - - - - - - - - - - - - - - - | /|\ defined by client application +--------+--------+ | | +----+-----+ +----+----+ | globCall | | XCall | +----------+ +---------+ | | | | V V global X::report Invoker contains a list of pointers to command objects which have been registered by the client code, in main() perhaps. We could have used references in the Register function design instead of pointers. When invoker detects an event requiring one of the callbacks it simply invokes the command objects execute() function to get the command, in this case either globCall or XCall, to call the global function or X member function, respectively. This is a very nice demonstration of the power of polymorphism. Because the library designer had enough foresight to create the command class, the client can derive any specialized command class needed to callback any client defined function. Because there is a common base class and invoker keeps a list of base class pointers the correct derived command object will be called, even thought the library designer would probably have no idea what the client code needed. The library simply provides the language in the command class so the client can do what is needed, and an invoker function which also knows the language. Note that the library only needs to know library things, and the client only needs to know client things plus the language provided by the library for its use (that's the purpose in life for proto- col classes). For example, invoker has some internally detected event that prompts callbacks. The client does not need to know anything about the event, and the invoker does not need to know anything about the activities of the functions it calls, not even their names! The essence of object oriented design is to use data abstraction and polymorphism to limit need to know. Encapsulating knowledge this way is what promotes loose coupling in an object oriented design. It is loose coupling that makes software easier to change, more robust, and more easily reusable. */ #include #include using namespace std; //----< defining a global function to be called back >------------ void global() { cout << " I'm the global function\n"; } //----< declaring class with member function to be called back >--- class X { public: void report() { cout << " I'm the X object's report function\n"; } }; // //----< declaring an abstract base class for commands >----------- class command { public: virtual void execute() = 0; }; //----< declaring a command class for global calls >-------------- class globCall : public command { public: globCall(void (*funptr)()) : fptr(funptr) { }; void execute() { (*fptr)(); } private: void (*fptr)(); // strong typing ties this class }; // to signatures void fun() //----< declaring a command class for X calls >----------------- class XCall : public command { public: XCall(X* ptr, void (X::*funptr)()) : cptr(ptr), fptr(funptr) { }; void execute() { (cptr->*fptr)(); } private: X* cptr; // strong typing ties this class void (X::*fptr)(); // to signatures void X::fun() }; //----< declaring an invoker class to make callbacks >------------ class invoker { public: void Register(command* c) { list.push_back(c); } void invoke() { vector::iterator it; for(it=list.begin(); it!=list.end(); ++it) (*it)->execute(); // polymorphic call } private: int next; // where next command will be registered vector list; // callback list of base class pointers }; // void main() { cout << "\n" << " =====================================================\n" << " Simulating callbacks to global and member functions \n" << " using command objects \n" << " =====================================================\n"; cout << "\n direct calls from command objects:\n"; void (*gptr)() = global; // pointer to global function globCall myGLcall(gptr); // command object for global myGLcall.execute(); // direct call to global() X myX; // a client X object X* myPtr = &myX; // pointer to X void (X::*fptr)() = &X::report; // pointer to X::report XCall myMFcall(myPtr,fptr); // command object for X::report myMFcall.execute(); // direct call to X::report cout << "\n callbacks invoked from command objects through invoker\n"; invoker inv; // callback invoker inv.Register(&myGLcall); // register global callback inv.Register(&myMFcall); // register X callback inv.invoke(); // callback simulation cout << "\n\n"; }