bertrand@eiffel.UUCP (Bertrand Meyer) (03/20/89)
Note: Vincent Kraemer from Interactive Software Engineering helped significantly with the work leading to this message. --------------------- BEGIN: GENERAL NOTE ----------------------------------- With this posting I am attempting to start a series of contributions describing useful and practical Eiffel techniques not well covered yet in the available Eiffel literature. I hope they will prove interesting to the readers of this newsgroup. Given the difficulty I have to complete the bimonthly installments of my column in the Journal of Object-Oriented Programming on time, the adjective ``weekly'' in the title appears rather bold. I don't promise regularity. Others from Interactive will contribute in the future. Suggestions and contributions from other readers will be greatly appreciated. (Of course anyone is welcome to post contributions in the same vein without going through me.) --------------------- END: GENERAL NOTE ----------------------------------- HINT NUMBER 1: CALL BACK MECHANISMS ----------------------------------- Several window systems (notably Suntools/Sunview/View2, with its ``notifier'', and X) have a mechanism known as ``call back''. The scheme can be generalized to other types of applications such as databases, although this discussion will be confined to the example of window systems. This mechanism allows for flexible event handling. In window systems, examples of events include mouse button down, mouse cursor entering window etc. X defines several dozen such events. The mechanism ensures among other things that: - For each event, a default handling mechanism is defined by the window system. (For example, the default handling for ``cursor entering window'' might be to make the window frame solid black, whereas the default for ``cursor leaving window'' changes the window frame to some other form.) - Unless the programmer specifies otherwise, every window will rely on the default mechanism to handle events that occur while the mouse cursor is in the window. - The default mechanism can be changed for a given window by attaching to it a function that describes the replacement handling. In the C world, this effect is achieved by including function pointers in the C structure types describing the windows (or other structures). These pointers are initially set to refer to functions corresponding to the default handlers. To override the default behavior, programmers may re-assign to these pointers the value of pointers to other functions. This works but is a typical low-level C technique involving pointer manipulations. It forces programmers to learn the intricacies of the internal implementations for data structures such as windows (not surprisingly, the manuals for the corresponding window systems are full of C code extracts describing these implementations, as if user specifications for electronic components forced you to learn the internals of the components!). Furthermore, the C approach is highly unsafe because the language does not permit static checking of the type compatibility of all assignable functions. The resulting bugs can be horrendous. Before seeing a cleaner solution as permitted by Eiffel techniques, we may note that in practice the implementation must address another important requirement: certain windows should ignore certain events. For example, menu windows will only deal by themselves with a small number of event types. Events ignored by a window are passed on to its parent (enclosing) window, which applies its own handling mechanism to the event (or may pass it further to its own parent). The problem thus defined admits a clean object-oriented solution, based on the Eiffel mechanisms for redefinition, polymorphism and dynamic binding. Define a general class, say X_WINDOW, of the following form: -------------------- BEGIN: X_WINDOW -------------------------- -- Instances of this class describe windows -- as implemented through the X Window system -- (Note: users of the Eiffel Graphical Library need -- only study this class and related ones, not the X -- documentation.) class X_WINDOW export handle, ignore, dont_ignore, ... feature handle (ev: EVENT) is -- General mechanism for handling ev do if ignored (ev) then parent.handle else if ``ev is MOUSE DOWN'' then mouse_down_handle elsif ``ev is CURSOR ENTER'' then cursor_enter_handle elsif .... end end end; -- handle mouse_down_handle is -- Default handling of MOUSE DOWN events do ... Default action for MOUSE DOWN event ... end; -- mouse_down_handle cursor_enter_handle is -- Default handling of CURSOR ENTER events do ... Default action for CURSOR ENTER event ... end; -- cursor_enter_handle ... Other routines describing default event handlers ... ignored (ev: EVENT): BOOLEAN is -- Is ev to be passed on to parent window? do ... ensure not parent.Void or else not Result -- In other words, if the window has no parent, -- the result must be false end; -- ignored ignore (ev: EVENT) is -- Make sure that ev will be passed on to parent window do ... ensure ignored (ev) end; -- ignore dont_ignore (ev: EVENT) is -- Make sure that ev will be handled by the window itself do ... ensure not ignored (ev) end; -- dont_ignore default_ignore_policy is -- Define which events are by default to be handled -- by the window itself, and which ones are to be passed -- to the parent do ... end; -- default_ignore_policy ... Other features ... end -- class X_WINDOW -------------------- END: WINDOW -------------------------- The postcondition to function ``ignored'' expresses that the window which has no parent, that is to say the root (outermost) window, may not ignore any event. In practice X_WINDOW may need to be deferred, although this is not evidenced by the present discussion. Our own design for the Eiffel Graphical Library defines a class WINDOW that attempts to cover both View2 (or other window systems) as well as X, although at the moment we are refining the X variant only through the above X_WINDOW class, which inherits from WINDOW. Obviously, a class WINDOW that can be a parent of both X_WINDOW and, say, VIEW2_WINDOW will need to be deferred. Once the above has been defined, the rest of the mechanism follows: to override the default mechanisms, descendants of X_WINDOW such as MENU, BUTTON etc. may redefine some of the event handling routines; for example one of these classes may redefine cursor_enter_handle, but leave the others untouched. They may also change the default ignore policy, again by redefinition. The general pattern, as defined by procedure ``handle'' in class X_WINDOW, does *not* need to be redefined. This is once again typical of how object-oriented design supports advanced reusability by factoring out the common behavior in an ancestor class, here X_WINDOW, and letting the descendants describe the variable details of such a general behavior. At execution-time, dynamic binding ensures that any call of the form w.handle will trigger the appropriate event handling, depending on what kind of window w happens to be dynamically. This applies in particular to the call parent.handle in the ``then'' clause of the conditional in routine ``handle'' Dynamic binding is complemented by static typing: the rules on the redefinition mechanism ensure type safety.