Choosing a Model/Event Pattern
The following diagrams show different implementation patterns of
models and corresponding events, as typically used in the UI. Depending
upon your needs, you can use the following tables to decide which
pattern to implement.
The patterns are split into model patterns and event patterns. Not all combinations of the patterns make sense, but patterns can be combined to fulfill your unique requirements. At the end I make some recommendations.
Model Pattern 1 - Automatic Event Firing
With the above pattern every change to the model results in an event being fired. Typically if the focus of a text field is lost, the model gets set (in Eclipse RCP perhaps using databinding). Any views that show this model attribute get notified and update themselves. Works well for cases where multiple views of the same model need to be continuously up to date. A big disadvantage is the number of events which get fired. You also need to watch out for cyclic dependencies whereby an event updates a second view, and that update fires another event which updates the first. UI performance can suffer with this pattern, because its granularity is simply too fine.
Model Pattern 2 - Manual Event Firing
This pattern lets the caller (typically the controller) fire an event after it has finished updating all parts of the model. Typically the controller performs any validation of multiple fields, may call the server and when everything is good, updates the UI's model. An example might be when an input dialog is closed and the data from that dialog's model needs to be incorporated in one single sweep into the main model. Advantages of this pattern are that events are only fired when all data has been validated together and you can be sure all data is coherent. The disadvantage with this pattern is that you rely on the caller to fire the event (the developer must remember to write that code!). What typically happens is that the same fire method gets called frequently. When implementing this pattern, you can choose if you supply one "fire" method which takes an enumerator, or whether you supply many methods, one for each relevant level of granularity.
Model Pattern 3 - Coarse Grained Model Methods
In order to reduce the number of events which are fired by Model Pattern 1, and reduce the problems of the caller having to fire events in Model Pattern 2, the above pattern provides coarse grained methods for updating multiple model parts together, in one "transaction". The model implementation sets all the relevant parts based on the given parameters, and only at the end fires a relevant or several relevant events (in the above example, perhaps an event that some elephants were added and an event that some hippos were added).
Model Pattern 4 - Model with Suspendable Event Firing
A different solution to the disadvantages of Model Patterns 1 & 2, is to allow the caller to suspend event firing temporarily. While this approach is interesting, its comes with dangers like losing events from concurrent model updates. For example, a user event causes a call to update a large amount of attributes. In the mean time, another user event causes a single attribute to be updated, but because event firing has been suspended as part of the first update, its event is never fired. The solution is that when resuming event firing, a generic event is fired, to tell all listeners that the entire model may have changed. While this pattern builds upon Model Patterns 1 & 2, it still suffers like Model Pattern 2, by relying on the caller to make a call to resume event firing at the end. Again, a controller can encapsulate these extra calls for resuming, so that they are at least all located in a logical place.
Event Pattern 1 - Listener is told about granularity of event
In the above pattern, the listener is given a hint about what has changed. The listener can then base its decision on what to refresh upon this context. Better implementations don't use Strings, rather use an object hierarchy, firstly so that the pattern becomes strongly typed, and secondly so that tests of "implements" can be carried out, so that the listeners implementation does not get filled full of "if" or "switch" statements. For example, a specific listener may be interested when any animal in the zoo is modified, whereby a different listener may be interested in specific events like elephants and hippos changing. With this pattern, the listener must have a reference to the model in order to get the refreshed data and as such may not know that only one item in a list has been changed, causing it to refresh more of the view than necessary, as might be the case with tables where only one row has changed.
Event Pattern 2 - Listener provides context specific methods
In this pattern, the listener is informed about fine or coarse grained events, but does not get told about say individual rows in a table which have changed, forcing the whole table to be updated. This pattern is also less flexible than Event Pattern 1, because the implementation cannot test for an events superclass. Effectively this pattern is like Event Pattern 1 when using Strings, only it is strongly typed. As such, Event Pattern 1 is more flexible.
Event Pattern 3 - Listener receives model parts - fine grained
With this pattern the listener is told exactly what has changed, which has the advantage that the listener only needs to update exactly what has changed, for example a single row in a table. This pattern implicitly relies on Model Pattern 1, and is hence fine grained.
Event Pattern 4 - Listener receives multiple model parts - coarse grained
This pattern extends Event Pattern 3 by becoming capable of handling multiple updates because the listener is given a number of changed items at the same time. It might be associated with Model Pattern 2, 3 or 4, where multiple updates are performed before the event is fired, however as shown, it is constrained because all changes need to be of the same type.
In my experience Model Pattern 3 and Event Pattern 1 are the best combination because they are the most flexible and reduce the number of events being fired which improves performance and usability. In cases where it is important for the view to only update certain rows of a table, the listener methods can be changed to also pass the changed objects to the listener, however this is only required in cases where UI performance would otherwise be really poor.
I would like the thank Mike Moor and Vera Fischer of the SBB for their inputs.