Design Log - Observers

May 31, 2009 17:08

I wasn't sure if I wanted to post patterns that I've worked with before. However, there have been a few uses of different patterns that surprised me before, so I want to capture as much information as possible on the chance that another usage for the pattern emerges.


My book uses the example of a weather station and multiple weather displays. The weather displays all rely on the weather station for their data. However, there are multiple types of weather displays, ranging from displays that present average temperature to displays that use barometric readings to predict rainstorms.

A naive implementation for the weather station would hardcode passing data to the displays whenever weather information changes. However, you lock yourself into the implementations of the weather displays. If the code for the weather displays change, you'll have to change the code in the weather station as well. Also, hardcoding the weather displays into the weather station means you won't be able to add more weather displays at runtime.

There are many times during application operation when an object's behavior depends on another object's state. You can have the observed objects explicitly tell each of its observers about its changes when they occur. However, in doing so you wind up locking yourself into using the implementation of the observers. By hardcoding these changes, you prevent runtime addition of any new observers.

Instead, whenever you have a bunch of objects whose behavior depends on another object's state, it's best to use break down the classes into interfaces: Subject and Observer. Any object that needs to update a bunch of other objects implements the Subject interface. Any object that needs to observer changes implements the Observer interface. The class implementing Subject doesn't need to know what type of object the observer is, as long as it implements the Observer interface. The class that implements Subject should contain a vector, or some other container of Observer-type objects. This way, the program can add more observers during runtime.

Going back to our example, the weather station implements the Subject interface, including Add/Remove Observer, and NotifyObserver. We can add or remove observers during runtime, as long as the observing objects also implement the Observer interface. Whenever weather data changes, we just call NotifyObserver, which should loop through all the observers and push new data out to them.

Our weather displays should implement the Observer interface. The observer interface comes with (at a minimum) the update call, which the Subject calls during NotifyObserver.

In our own code base, we use Subject/Observer extensively in our property system (which makes sense). We have three types of entities - actors (which we've covered previously), behaviors, and aspects. Behaviors define the runtime actions of an actor. If we want an actor to change color, we add a behavior that changes color attributes on the actor. Aspects represent different behaviors of an actor (yeah, the names are a little off). The main aspect is the motionAspect, which updates movement, and responds to external stimuli by changing movement. One last type of entity is the attribute, which encapsulates data and a name for that data.

Actors, behaviors, and aspects can all observe attributes, so that when an attribute changes, any one of those entities can respond appropriately. For instance, we could have a Move Behavior that adds a velocity of 45,45 to a motionAspect's linearVelocity attribute. As an observer of its own linearVelocity attribtue, the motionAspect will react to changes in its linearVelocity by updating the linear velocity in the actor's physics object.

Ok, looking over that, I realize that this is not only a bad example of when to use the observer pattern, but also shows how much different objects rely on the implementation of other objects. If we ever decide to change the implementation of motionAspect's linearVelocity (maybe we decide to change it from an attribute), we'd have to update a bunch of other files, introducing opportunities for bugs.

Here's another example - the motionAspect observes its actor's position. If the actor's position changes, the motionAspect gets notified, and does calculations to see if the actor has wandered off the screen.

work

Previous post Next post
Up