Software Development - Software Design

Design Patterns

URL Description
Observer Pattern - Consequences

  1. Generally, you need to make sure that the change of state is complete and that the object has a consistent state before notifying observers of the change.
  2. Be clear about whether the order in which Observer objects are notified is or is not predictable.
  3. Do not let an Observer register itself twice with the same Observed object unless you're sure that's what it means to do.

The Observer Pattern Revisited

Pitfalls of the Observer Pattern

Observer implementations can easily result in undesirable run-time behaviour characterised by observers performing multiple updates and observer references creating dangling references that inhibit garbage collection. Complex interrelationships between observables and observers result in relationship hierarchies where an object can function as both an observer and an observable. Such an object generates the same events that it must respond to. For brevity and clarity, figure two uses the alternative denotation of broadcaster-listener for the observer pattern. Listeners one and three create a cycle by also being broadcasters. Listener one is dependent on two broadcasters which results in listener one receiving two notifications within a single update operation initiated by broadcaster one.

Why should the observer pattern be deprecated?

Example of Observer Pattern

var path: Path = null
val moveObserver = { (event: MouseEvent) =>
   path.lineTo(event.position)
   draw(path)
}
control.addMouseDownObserver { event =>
   path = new Path(event.position)
   control.addMouseMoveObserver(moveObserver)
}
control.addMouseUpObserver { event =>
   control.removeMouseMoveObserver(moveObserver)
   path.close()
   draw(path)
}

The Observer Pattern has some major design issues:

Side-effects

Observers promote side-effects. Since observers are stateless, we often need several of them to simulate a state machine as in the drag example. We have to save the state where it is accessible to all involved observers such as in the variable path above.

Encapsulation

As the state variable path escapes the scope of the observers, the observer pattern breaks encapsulation.

Composability

Multiple observers form a loose collection of objects that deal with a single concern (or multiple, see next point). Since multiple observers are installed at different points at different times, we can’t, for instance, easily dispose them altogether.

Separation of concerns

The above observers not only trace the mouse path but also call a drawing command, or more generally, include two different concerns in the same code location. It is often preferable to separate the concerns of constructing the path and displaying it, e.g., as in the model-view-controller (MVC) [30] pattern.

Scalablity

We could achieve a separation of concerns in our example by creating a class for paths that itself publishes events when the path changes. Unfortunately, there is no guarantee for data consistency in the observer pattern. Let us suppose we would create another event publishing object that depends on changes in our original path, e.g., a rectangle that represents the bounds of our path. Also consider an observer listening to changes in both the path and its bounds in order to draw a framed path. This observer would manually need to determine whether the bounds are already updated and, if not, defer the drawing operation. Otherwise the user could observe a frame on the screen that has the wrong size (a glitch).

Uniformity

Different methods to install different observers decrease code uniformity.

Abstraction

There is a low level of abstraction in the example. It relies on a heavyweight interface of a control class that provides more than just specific methods to install mouse event observers. Therefore, we cannot abstract over the precise event sources. For instance, we could let the user abort a drag operation by hitting the escape key or use a different pointer device such as a touch screen or graphics tablet.

Resource Management

An observer’s life-time needs to be managed by clients. Because of performance reasons, we want to observe mouse move events only during a drag operation. Therefore, we need to explicitly install and uninstall the mouse move observer and we need to remember the point of installation (control above).

Semantic distance

Ultimately, the example is hard to understand because the control flow is inverted which results in too much boilerplate code that increases the semantic distance between the programmers intention and the actual code.