Part IV Gang of Four Patterns 16 Gang of Four Design Patterns
31.9 Event Manager Sample Code
To illustrate the use of the Mediator and Colleague traits we will explore a typical UI scenario. In this scenario various different GUI elements need to communicate.
For example, when a change is made in a form, the overall frame needs to change its display to show that a save is required, the save button may need to be enabled and the logger may need to record the user activity for future audit trails etc.
We have decided to use the Mediator pattern to simplify the communication between these different components. We have also chosen to use an Event class as the basis of the data exchanged. The Event takes a reference to the object that created the event and the data provided as the payload of the event (in this case a string).
This Event class is then subclassed into specifi c types of event such as ActionEvent, SaveEvent and MouseEvent. MouseEvent adds information about the x and y coordinates of the mouse when it was clicked.
The concrete Mediator class is then defi ned with respect to the Event class:
The concrete EventMediator extends the Mediator trait and provides a concrete implementation of the ColleagueCallback type and the notify method. The ColleagueCallback type is defi ned as a function that takes an event as a parameter and return Unit (that is it does not return a value). The notify method uses pattern matching to valid the type of the parameter passed in (which should now be an Event type) and invokes each of the functions in the colleagues list buffer passing in the event as a parameter to those functions.
Note that because the ListBuffer defi ned in the Mediator pattern is defi ned to hold only ColleagueCallbacks, this means that when mixed into the EventMediator it will hold functions that take an Event and return Unit. Therefore we are able to invoke each function, passing in the event to that function within the foreach.
Also note that we could have used asInstanceOf to convert the parameter e into an Event, however we have used pattern matching as this is consider more idiomatic Scala.
A companion object is used to provide simple access to the singleton instance of the EventMediator that will be used in this example. This is a design choice, there is no specifi c reason why the EventMediator should be a singleton object other than it makes this example straight forward to work with. There could equally be sepa- rate instances of the EventMediator that are used with different parts of the GUI thus allowing controlled communication between them. For example, there could be an EventMediator for the Admin oriented components and a separate one for the Update oriented components. These components would then need to access the EventMediator they are expected to work within the appropriate way.
We can now defi ne a set of components that can play the role of a group of communicating Colleagues: LogPanel, Form and Frame.
The LogPanel object is intended as a simple output only panel that displays mes- sages indicating what is happening within the application. This might be useful both for debugging purposes and for user notifi cation uses. In this sample application the LogPanel merely prints a message to the console whenever the log method is called.
The log method takes an event, return Unit and prints the event as part of the log message. The LogPanel also extends the Colleague marker trait.
As such the log method meets the contract specifi ed by the ColleagueCallback type and thus can be used with the EventMediator.
Note that we have chosen to use objects for the actual Colleague instances; this is a choice; we could have used classes or further traits and instantiated them when required.
The Form object also extends the Colleague trait and defi nes three methods. One method, receive , meets the contract specifi ed by the ColleagueCalback type in the EventMediator and so can be registered with the mediator. The other two methods take no parameters and instead perform logic associated with the Form and then
notify the EventMediator that something has happened. In the case of the Update method it prints a message (simulating some user activity) and then publishes an ActionEvent to the EventMediator. It access the EventMediator using the compo- nent objects instance property.
Finally, the frame object is shown below. This object also extends the Colleague trait. It posses two methods, handleEvent which meets the ColleagueCallback con- tract of the EventMediaotr and a zero parameter input method. The input method notifi es the mediator of a MouseEvent. It also possess a requiresSave property that is changed from true to false (and back again) depending upon the activity of other colleagues.
The handleEvent method is worth examining in some more detail. In this case the method is a little more sophisticated than those shown above. Specifi cally, the handleEvent method takes different actions depending upon whether the event type received is a MouseEvent, an ActionEvent a SaveEvent or some other yet to be defi ned Event type. It does this using pattern matching which is a common code oriented pattern in Scala. If an ActionEvent is received it sets the requiresSave prop- erty to true. If a SaveEvent is received it sets the requiresSave property to false.
In addition the Frame object also distinguishes between MouseEvents sent by itself, which it ignores, and those sent by other Colleagues in which case it takes appropriate action. This is why the source of the event was included in the event class; it allows Colleagues to determine whether the event was raised by themselves and ignore if they wish.
The Test application, which constructs the mediator and its colleagues, is pre- sented below. The mediator registers each of the ColleagueCallback compliant functions provided by the three colleagues with the mediator by appending them to the mediator. Notice that although we are using the term method and function
interchangeably here as the method (which is essentially a function tied to the object) also meets the interface specifi cation defi ned by the ColleageCallback type in the EventMediator.
Note that in this example, for simplicity, the Colleagues are registered with the mediator in the wrapping application (in this case Test). However, the Colleagues could have registered themselves with the mediator as part of the setup process.
Once all mediator and the colleagues are set up, the Test application calls the input method on the Frame, and the update and save methods on the Form. When these methods are invoked communication between the colleagues is started. The output from running this program is shown below: