September 30, 2012

Observable properties: A means to track state changes in Java objects

An example application of the Observer design pattern using Java


In one of my recent pet projects I needed objects that should be "aware" that one or more of their properties had changed. The obvious way to do it of course is to write code inside the "setter method" of each property and call whatever code must be executed in the event of a change. Something like that:

void setName(String name) {
  this.name = name;
  onNameChanged();
}

void onNameChanged() {
  // do whatever is necessary here
}

But what I really wanted was to attach an "on change" event to some of the object's properties and handle this event in a systematic way. I thought this was a valid opportunity to use the Observer design pattern and have the properties of the object notify their owner that they had changed.

We can assume that each property is not any more a simple object of a given type T, but an instance of an "observable object" that contains a value of type T:

public final class ObservableProperty<T> {

  private PropertyObserver _observer;
  private T _value;

  public T getValue() {
    return _value;
  }

  public void setValue(T newValue) {
    _value = newValue;
    _observer.notify();
  }
}

The observable object must be able to notify the observer by calling its notify() method, so the PropertyObserver object must have a method about like that:

public class PropertyObserver {
  // other methods and fields
  void notify();
  // other methods and fields
}

The above solution, although valid, seems a little spartan. The PropertyObserver may be notified by calls to its notify() method, but it receives no information whatsoever about which property has changed or how it has changed. We should create a more interesting IPropertyObserver interface:

public interface IPropertyObserver {
  void onPropertyChanged(ObservableProperty<?> sender, Object previousValue);
}

With this new implementation, the property observer object can be notified that a property has changed and also it may receive information about which property changed (the “sender” argument) and what was the value of the property before the change (the “previousValue” argument).

To cooperate with IPropertyObserver, the implementation of the ObservableProperty generic class must now change to something like this:

public final class ObservableProperty<T> {

  private final IPropertyObserver _observer;
  private T _value;

  public T getValue() {
    return _value;
  }

  public void setValue(T newValue) {
    if (newValue == _value || (newValue != null && newValue.equals(_value))) {
      return; // The property has not changed
    }
    Object previousValue = _value;
    _value = newValue;
    _observer.onPropertyChanged(this, previousValue); // Notify the observer
                                                      // about the change
  }

  public ObservableProperty(IPropertyObserver observer) {
    super();
    this._observer = observer;
  }
}

This new implementation also checks to see that the internal value of the property has in fact changed, before calling onPropertyChanged().

To try out this simple solution, let's create a simple Contact object; an object to hold the name and contact information of a person:

class Contact implements IPropertyObserver {

  public final ObservableProperty<String> name = new ObservableProperty<String>(this);
  public final ObservableProperty<Date> birthDate = new ObservableProperty<Date>(this);
  public final ObservableProperty<String> mobile = new ObservableProperty<String>(this);
  public final ObservableProperty<String> email = new ObservableProperty<String>(this);

  public void onPropertyChanged(ObservableProperty<?> sender, Object previousValue) {
  }
}

The Contact class, above, has four properties (name, birthDate, email, mobile) that are instances of ObservableProperty. Also, it implements the interface IPropertyObserver, so that it may be notified every time one of its observable properties has changed.

We now have a central place, where we can handle the event of a change in the values of the observable properties. The following code, for instance, will reflect on the object to find the property's name and then display an informative message reporting both the new and the old value of the property:

public void onPropertyChanged(ObservableProperty<?> sender, Object previousValue) {
  try {
    for (Field f : Contact.class.getDeclaredFields()) {
      if (f.get(this) == sender) {
        System.out.println(String.format("%33s %-28s (was: %s)", "'" +
          f.getName() + "' changed to:", sender.getValue(), previousValue));
      }
    }
  }
  catch (RuntimeException e) {
    throw e;
  }
  catch (Exception e) {
    throw new RuntimeException(e);
  }
}

No comments:

Post a Comment