3.16.1. Observable Value Schnittstelle und Implementierungen

Im Folgenden wird die Schnittstelle IObservableValue vorgestellt und es werden existierende Default Implementierungen gezeigt.

Die Schnittstelle IObservableValue

Die Schnittstelle IObservableValue sieht wie folgt aus:

  1  public interface IObservableValue<VALUE_TYPE> {
  2  
  3      void setValue(VALUE_TYPE value);
  4  
  5      VALUE_TYPE getValue();
  6  
  7      void addValueListener(IObservableValueListener<?> listener);
  8  
  9      void removeValueListener(IObservableValueListener<?> listener);
 10  }

Ein IObservableValueListener hat die folgende Methode:

    void changed(IObservableValue<VALUE_TYPE> observableValue, VALUE_TYPE value);

Man bekommt sowohl den Observable Value der sich geändert hat, als auch den neuen Wert (value) übergeben. Der Listener feuert nur dann, wenn sich der Wert tatsächlich geändert hat. Wird zum Beispiel ein zweites mal der gleiche Wert gesetzt, wird kein ChangedEvent geworfen.

ObservableValue Default Implementierung

Die Klasse ObservableValue bietet eine Default Implementierung der Schnittstelle IObservableValue.

Das folgende Beispiel demonstriert die Verwendung der Klasse ObservableValue:

  1      final IObservableValue<IPerson> forkliftDriver = new ObservableValue<IPerson>(dieter);
  2          
  3      forkliftDriver.addValueListener(new IObservableValueListener<IPerson>() {
  4          @Override
  5          public void changed(final IObservableValue<IPerson> observableValue, final IPerson value) { 
  6              diaphone.setActive(value == klaus);
  7          }
  8      });
  9          
 10      forkliftDriver.setValue(klaus);

Die Default Implementierung implementiert nicht equals() und hashCode(). Zwei ObservableValue Objekte sind insbesondere nicht equal, wenn ihre values gleich sind. Der folgende UnitTest soll verdeutlichen, warum:

  1      final ObservableValue<String> value = new ObservableValue<String>();
  2      value.setValue(STRING_1);
  3  
  4      final Set<ObservableValue<String>> set = new HashSet<ObservableValue<String>>();
  5      set.add(value);
  6  
  7      value.setValue(STRING_2);
  8  
  9      Assert.assertTrue(set.remove(value));

Durch das Ändern des Wertes in Zeile 7 würde sich der hasCode() ändern, wodurch der value nicht mehr aus der Liste entfernt werden könnte.

Die Klasse ObservableValue wurde so entworfen, dass davon abgeleitet werden kann. Das folgenden Beispiel zeigt eine ObservablePerson, welche equals() und hashCode() mit Hilfe einer id implementiert:

  1  import org.jowidgets.util.Assert;
  2  import org.jowidgets.util.ObservableValue;
  3  
  4  public final class ObservablePerson extends ObservableValue<IPerson> {
  5  
  6      private final Object id;
  7  
  8      public ObservablePerson(final Object id) {
  9          Assert.paramNotNull(id, "id");
 10          this.id = id;
 11      }
 12  
 13      @Override
 14      public int hashCode() {
 15          final int prime = 31;
 16          int result = 1;
 17          result = prime * result + ((id == null) ? 0 : id.hashCode());
 18          return result;
 19      }
 20  
 21      @Override
 22      public boolean equals(final Object obj) {
 23          if (this == obj) {
 24              return true;
 25          }
 26          if (obj == null) {
 27              return false;
 28          }
 29          if (!(obj instanceof ObservablePerson)) {
 30              return false;
 31          }
 32          final ObservablePerson other = (ObservablePerson) obj;
 33          if (id == null) {
 34              if (other.id != null) {
 35                  return false;
 36              }
 37          }
 38          else if (!id.equals(other.id)) {
 39              return false;
 40          }
 41          return true;
 42      }
 43  
 44  }
ObservableValueWrapper

Um ein IObservableValue mit Hilfe des Wrapper Patters zu wrappen, zum Beispiel um den Wert zu dekorieren, kann die Klasse ObservableValueWrapper verwendet werden. Das folgende Beispiel soll das verdeutlichen:

  1  import org.jowidgets.util.IDecorator;
  2  import org.jowidgets.util.IObservableValue;
  3  import org.jowidgets.util.ObservableValueWrapper;
  4  
  5  public final class ObservableValueDecorator<VALUE_TYPE> 
  6      implements IDecorator<IObservableValue<VALUE_TYPE>> {
  7  
  8      //injected
  9      private IAuthorizationService authorizationService;
 10  
 11      @Override
 12      public IObservableValue<VALUE_TYPE> decorate(final IObservableValue<VALUE_TYPE> original) {
 13          if (authorizationService == null) {
 14              return original;
 15          }
 16          else {
 17              return new ObservableValueWrapper<VALUE_TYPE>(original) {
 18                  @Override
 19                  public void setValue(final VALUE_TYPE value) {
 20                      if (!authorizationService.hasAuthorization(Authorizations.UPDATE)) {
 21                          throw new SecurityException("No authorization for update");
 22                      }
 23                      else {
 24                          super.setValue(value);
 25                      }
 26                  }
 27              };
 28          }
 29      }
 30  }
MandatoryObservableValue

Die Klasse MandatoryObservableValue liefert eine Implementierung von IObservableValue welche nicht den Wert null annehmen kann. Die Implementierung sieht wie folgt aus:

  1  public class MandatoryObservableValue<VALUE_TYPE> extends ObservableValue<VALUE_TYPE> {
  2  
  3      private final VALUE_TYPE defaultValue;
  4  
  5      public MandatoryObservableValue(final VALUE_TYPE defaultValue) {
  6          Assert.paramNotNull(defaultValue, "defaultValue");
  7          this.defaultValue = defaultValue;
  8      }
  9  
 10      @Override
 11      public void setValue(final VALUE_TYPE value) {
 12          if (value != null) {
 13              super.setValue(value);
 14          }
 15          else {
 16              super.setValue(defaultValue);
 17          }
 18      }
 19  
 20      @Override
 21      public VALUE_TYPE getValue() {
 22          final VALUE_TYPE superResult = super.getValue();
 23          if (superResult != null) {
 24              return superResult;
 25          }
 26          else {
 27              return defaultValue;
 28          }
 29      }
 30  }

Ein MandatoryObservableValue hat einen Default Value (Zeile 3), welcher verwendet wird, sobald null gesetzt wird.

ObservableBoolean

Ein ObservableBoolean liefert eine Implementierung von IObservableValue für ein Boolean bei dem nicht gewünscht ist, dass der Wert null angenommen werden kann, sondern nur true und false. Die Implementierung sieht wie folgt aus:

  1  public final class ObservableBoolean extends ObservableValue<Boolean> {
  2  
  3      public ObservableBoolean() {
  4          setValue(false);
  5      }
  6  
  7      public ObservableBoolean(final boolean value) {
  8          set(value);
  9      }
 10  
 11      public boolean get() {
 12          return getValue().booleanValue();
 13      }
 14  
 15      public void set(final boolean value) {
 16          super.setValue(Boolean.valueOf(value));
 17      }
 18  
 19      @Override
 20      public void setValue(final Boolean value) {
 21          Assert.paramNotNull(value, "value");
 22          super.setValue(value);
 23      }
 24  }

Die Methoden get() und set() bieten einen komfortablen Zugriff auf den kleinen boolean ohne Autoboxing.[26] Der Ausdruck boolean b = get() kann (im Vergleich zu boolean b = getValue() auf einen herkömmlichen Observable Value) nie eine NullPointerException werfen, da man das Setzen von null explizit verhindert (Zeile 21). Man sollte einen solchen Wert nur an Observable Values binden, die ebenfalls Mandatory sind. Man könnte den Code ab Zeile 21 auch wie folgt ändern:

  1      @Override
  2      public void setValue(final Boolean value) {
  3          if (value != null){
  4              super.setValue(value);
  5          }
  6          else{
  7              super.setValue(Boolean.FALSE);
  8          }
  9      }

Dies würde dem Verhalten des MandatoryObservableValue entsprechen, wobei man zusätzlich noch die sichere get() Methode hat.



[26] Warum Atoboxing evil ist, wird unter anderem auch hier diskutiert: [https://pboop.wordpress.com/2010/09/22/autoboxing-is-evil/]


Siehe auch PDF Version dieses Dokuments, Jowidgets API Spezifikation