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/]