Im Folgenden wird die Schnittstelle
IObservableValue
vorgestellt und es werden
existierende Default Implementierungen gezeigt.
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.
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 }
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 }
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.
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/]