[comp.lang.eiffel] Eiffel hint of the week #1: Programming callback mechanisms

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.