Outline Observer Pattern: Pitfalls NotifyObservers Invocation Problem M:N Problem ConcurrentModificationException Problem Cyclic Dependency Problem Causality of State Changes Problem Memory Management Problem Multi Model Problem Observer Applications 23 March 2018 1
Observer Pattern: Structure Structure diagram Subject and Concrete Subject may be combined in a single class Observer may be an interface 23 March 2018 2
Observer Pattern: Collaborations :ConcreteSubject :ConcreteObserver :ConcreteObserver setstate notify update update 23 March 2018 3
Observer Pattern: Sample Code (1/2) interface Observer { void update(); abstract class Observable { private final List<Observer> observers = new ArrayList<>(); public void addobserver(observer o) { observers.add(o); public void removeobserver(observer o) { observers.remove(o); protected void notifyobservers() { for(observer obs : observers) { obs.update(); 23 March 2018 4
Observer Pattern: Sample Code (2/2) class Sensor extends Observable { private int temp; public int gettemperature() { return temp; public void settemperature(int val) { temp = val; notifyobservers(); class SensorObserver implements Observer { private Sensor s; SensorObserver (Sensor s) { this.s = s; s.addobserver(this); public void update() { System.out.println("Sensor has changed, new temperature is " + s.gettemperature()); 23 March 2018 5
Invocation of notifyobservers When is notifyobservers called Automatically at every state change => many updates Explicitly after a set of change operations => client responsibility Approaches to reduce number of updates Asynchronous update at idle time AWT/Swing repaint registers an asynchronous request that this component needs to be repainted => multiple calls to repaint may be combined Here, "observer" is the actual "paint" method which reacts on changes java.util.observable: explicit, but change operations call setchanged notifyobservers becomes only active if ischanged() == true (notifyobservers calls clearchanged) notifyobservers could be invoked regularly (as a background task) Registration for specific message types only To be checked within observable class 23 March 2018 6
java.util.observable public interface Observer { void update(observable o, Object arg); public class Observable { private boolean changed = false; // Marks this Observable as having been changed protected void setchanged() { changed = true; // Indicates that this object has no longer changed, // or that it has already notified all of its observers // of its most recent change. protected void clearchanged() { changed = false; // Tests if this object has changed. public boolean haschanged() { return changed; 23 March 2018 7
java.util.observable // If this object has changed, as indicated by the // haschanged method, then notify all of its observers // and then call the clearchanged method to indicate // that this object has no longer changed. public void notifyobservers(object arg) { if (!changed) return; Object[] copy = obs.toarray(); clearchanged(); // before notification! for (int i = copy.length-1; i>=0; i--) ((Observer)copy[i]).update(this, arg);... 23 March 2018 8
Missing Multiple Inheritance java.util.observable Provides implementation of an Observable Can only be used if extension is not defined in its own inheritance hierarchy Model Observable * Observer Consequence Code duplication java.util.observable is not used in the Java library (and will be removed) Solution Composition (twin classes) ObservableModel 23 March 2018 9
Observable Twin Classes Twin Classes Model Observable * Observer Observable- Model Observer- Registry getsource Concrete- Observer ObservableModel can be used where Models are expected ObserverRegistry can be used where Observables are expected public class Model { private int value; public int getvalue() { return value; public void setvalue(int value) { this.value = value; 23 March 2018 10
Observable Twin Classes Twin Classes public class ObservableModel extends Model { private final ObserverRegistry<Model> reg = new ObserverRegistry<>(this); public Observable getobservable() { return reg; public void addobserver(observer obs) { this.reg.addobserver(obs); @Override public void setvalue(int value) { super.setvalue(value); reg.setchanged(); reg.notifyobservers(); 23 March 2018 11
Observable Twin Classes Twin Classes public class ObserverRegistry<T> extends Observable { private final T owner; public ObserverRegistry(T trueobservable) { this.owner = trueobservable; public T gettrueobservable() { return owner; // lift visibility into this package protected void setchanged() { super.setchanged(); protected void clearchanged() { super.clearchanged(); Lifting necessary. setchanged is declared protected, but in another package! 23 March 2018 12
Observable Twin Classes Twin Classes public class PrintObserver implements Observer { public void update(observable obs, Object arg) { Model m = ((ObserverRegistry<Model>) obs).gettrueobservable(); System.out.println("new value: " + m.getvalue()); public class Test { public static void main(string[] args) { ObservableModel model = new ObservableModel(); Observer obs = new PrintObserver(); model.addobserver(obs); model.setvalue(5); model.setvalue(10); 23 March 2018 13
Many Observables / Observers Observable Observable Observable Observable Observable Observer Observer Observer Observer Observer 23 March 2018 14
Many Observables / Observers Observable Observable Observable Observable Observable Observer Observer Observer Observer Observer 23 March 2018 15
Many Observables / Observers Observable Observer Mediator public class Mediator extends Observable implements Observer { public void update(object source, Object arg){ notifyobservers(source, arg); 23 March 2018 16
Problem: Concurrent Modification in notify public void addobserver(observer o) { observers.add(o); public void removeobserver(observer o) { observers.remove(o); protected void notifyobservers(object arg) { for(observer obs : observers) { obs.update(this, arg); Problem: ConcurrentModificationException Collection over which one iterates must not be changed public class OnceObserver implements Observer { public void update(observable source, Object arg) { System.out.println("received update from "+source); source.removeobserver(this); 23 March 2018 17
Problem: Concurrent Modification in notify Solution 1: Perform notification on a copy of the observer list protected void notifyobservers() { Observer[] copy; copy = observers.toarray(new Observer[observers.size()]); for (Observer obs : copy) { obs.update(this); protected void notifyobservers() { for(observer o : new ArrayList<>(observables)) { o.update(this); 23 March 2018 18
Problem: Concurrent Modification in notify Solution 2: Delay add/removeobserver calls private static class Mutation { private final Observer observer; private final boolean add; public Mutation(Observer observer, boolean add) { this.observer = observer; this.add = add; private List<Mutation> pendingmutations = new LinkedList<>(); private int level = 0; public void addobserver(observer o) { if(level > 0) { pendingmutations.add(new Mutation(o, true)); else { observers.add(o); public void removeobserver(observer o) { if(level > 0) { pendingmutations.add(new Mutation(o, false)); else { observers.remove(o); 23 March 2018 19
Problem: Concurrent Modification in notify... protected void notifyobservers() { level++; for (Observer obs : observers) obs.update(this); level--; if(level == 0) { for(mutation m : pendingmuts) { if(m.add) observers.add(m.observer); else observers.remove(m.observer); pendingmutations.clear(); 23 March 2018 20
Problem: Concurrent Modification in notify Solution 3: Copy the observer list upon modification private List<Observer> observers = new CopyOnWriteArrayList<>(); public void addobserver(observer o) { observers.add(o); public void removeobserver(observer o) { observers.remove(o); protected void notifyobservers(object arg) { for(observer obs : observers) { obs.update(this, arg); CopyOnWriteArrayList is a (thread-safe) variant of ArrayList in which all mutative operations are implemented by making a fresh copy of the underlying array. 23 March 2018 21
Problem: Cyclic Dependencies ItemListener JCheckBox- MenuItem ColorModel ColorListener 23 March 2018 22
Problem: Cyclic Dependencies Notification Sequence 23 March 2018 23
Problem: Cyclic Dependencies Solution 1: Break recursion in the color listener implementation public void colorvaluechanged(color c) { if (isselected()!= c.equals(this.color)) setselected(c.equals(this.color)); Solution 2: Break recursion in (subclass of) JCheckBoxMenuItem public void setselected(boolean b) { if (b!= isselected()) super.setselected(b); 23 March 2018 24
Problem: Cyclic Dependencies Solution 3: Break recursion in ItemListener implementation private boolean updating = false; public void itemstatechanged(itemevent e) { if(!updating) { updating = true; if (e.getstatechange() == ItemEvent.SELECTED) { model.setcolor(color); updating = false; instead of comparing the value to be set on the model with the existing value, this solution checks for recursive invocations using a flag => changes performed during a notification would not be handled 23 March 2018 25
Problem: Cyclic Dependencies Solution 4: Break recursion in the color model public class ColorModel { private Color color; public void setcolor(color color) { if (!color.equals(this.color)) { this.color = color; notify(color);... General rule: Propagate changes only if the model really changed 23 March 2018 26
Problem: Cyclic Dependencies Solution 5: Use ActionListener instead of an ItemListener Actions are only fired if an event is initiated over the keyboard or over the mouse Whenever the color is changed over the ColorModel, then the JCheckBoxMenuItem is changed and an ItemEvent is fired, but no listener is registered for that and thus the recursion is broken General Rule: Avoid recursion! Always break the recursion in a model 23 March 2018 27
Problem: Causality of Changes Causality How can we assert that notifications appear at the Observers in the same order as they were applied to the model 23 March 2018 28
Example Observable Document Observers CorrectionListener TextFieldListener Operation (c) is added in front of FHNW 23 March 2018 29
Problem: Causality of Changes Solutions Queuing of notifications: Notifications triggered by recursive state changes are delayed until all pending changes have been notified Model and notifications are out of sync, i.e. when an insert character is notified, this character may already have been removed from the model Queuing of state changes Model becomes asynchronous, i.e. state change operations are not executed immediately => difficult to program Prohibit state changes during notification 23 March 2018 30
Problem: Memory Management Example ActionListener * 1 Multicast- Panel newbutton new: JButton {Creates closeallbutton closeall: JButton SimpleFrame buf 23 March 2018 31
Problem: Memory Management MulticastPanel: ActionListener (new button) SimpleFrame f = new SimpleFrame(); f.settitle("window " + counter++); f.setsize(250, 150); f.setvisible(true); closeallbutton.addactionlistener(f); SimpleFrame: ActionListener (close all button) public void actionperformed(actionevent evt) { this.dispose(); // Releases all of the native screen // resources used by this window 23 March 2018 32
Problem: Memory Management Memory Leaks Despite the existence of a GC, memory leaks can occur Remember to remove registered observers before you release an object public void actionperformed(actionevent evt) { JButton closeallbutton = (JButton)evt.getSource(); closeallbutton.removeactionlistener(this); this.dispose(); Alternative solution: use weak references means that the listener is only registered in the observable as long as there exists a strong reference to it 23 March 2018 33
Problem: What is the model Multi-Models Assume that we represent each channel of our color as a separate model Simplifies implementations of the scrollbars / textfields red green blue Consequences Rules Each change of the color implies three updates Modelling is too fine granular, actually implementation details are published (like the representation of the color as RGB or HSV) Only changes from valid to valid state should be visible 23 March 2018 34
Outline Observer Pattern: Pitfalls Observer Applications Model-View Separation in Swing Other applications 23 March 2018 35
Model-View Separation in Swing All Swing controls refer to a model Model contains the data Model informs registered views when state changes View visualizes data View getmodel() setmodel(model m) * 1 Model Examples: JButton (ButtonModel) JScrollBar (BoundedRangeModel) JList (ListModel) JTree (TreeModel) JMenu (ButtonModel) 23 March 2018 36
Model-View Separation in Swing View: setmodel If the model is set on a view, then the view registers itself (or an explicit listener implementation) as an observer of the model class View { private Model model; public Model getmodel(){ return model; public void setmodel(model model){ Model m = getmodel(); if(m!= null){ m.removemodellistener(ml); this.model = model; model.addmodellistener(ml);... 23 March 2018 37
Model-View Separation in Swing Applications Several Controls may refer (display) the same model JTextField field = new JTextField(10); JPasswordField pwfld = new JPasswordField(10); JTextArea area = new JTextArea(5, 20); pwfld.setdocument(field.getdocument()); area.setdocument(field.getdocument()); 23 March 2018 38
Model-View Separation in Swing Applications Model may be implemented by user Example: ListModel interface ListModel { getsize(); getelementat(int i); adddatalistener(datalistener dl); removedatalistener(datalistener dl); List entries can be computed on demand List entries can be fetched from data base ListModel may dynamically change its content 23 March 2018 39
Model-View Separation in Swing Example: Fibonacci-Model N-th fibonacci number is computed on demand JList list = new JList( new AbstractListModel() { public int getsize() { return 10000; public Object getelementat(int n) { if(n == 0) return BigInteger.ZERO; BigInteger f0 = BigInteger.ZERO; BigInteger f1 = BigInteger.ONE; List entries can be computed on demand for(int i=1; i<n; i++) { List BigInteger entries can f2 be fetched = f0.add(f1); from data f0 base = f1; f1 = f2; ListModel may dynamically change its content return f1; ); 23 March 2018 40
Observer Pattern: Other Applications AWT Event handling ActionListener, MouseListener, MouseMotionListener, Swing: Model-View separation JTable, JList Java Beans Event Notifications public void addpropertychangelistener(propertychangelistener) public void removepropertychangelistener(propertychangelistener) public void addvetoablechangelistener(vetoablechangelistener) public void removevetoablechangelistener(vetoablechangelistener) JavaFX data binding 23 March 2018 41
JavaFX Data Binding 23 March 2018 42
JavaFX Data Binding ChangeListener Can be added to every ObservableValue void addlistener(changelistener<? super T> listener); Change listener is invoked as soon as the value changes If the value is not changed => no invocation => no cycles Both the old and the new value is passed to the listener textfield.textproperty().addlistener( (observable, oldvalue, newvalue) -> { //... ); 23 March 2018 43
JavaFX Data Binding Data Binding Properties can be bound unidirectionallly or bidirectionally StringProperty p1 = new SimpleStringProperty("p1"); IntegerProperty p2 = new SimpleIntegerProperty(); p2.bind(p1.length()); The value of p2 can no longer be set, but it depends on p1 StringProperty prop1 = new SimpleStringProperty(""); StringProperty prop2 = new SimpleStringProperty(""); prop2.bindbidirectional(prop1); If one value changes, the other is adjusted automatically 23 March 2018 44
JavaFX Data Binding Bidirectional Binding public static <T> BidirectionalBinding bind( Property<T> property1, Property<T> property2) { BidirectionalBinding binding = new TypedGenericBidirectionalBinding<T>(property1, property2); property1.setvalue(property2.getvalue()); property1.addlistener(binding); property2.addlistener(binding); return binding; 23 March 2018 45
JavaFX Data Binding Bidirectional Binding private static class TypedGenericBidirectionalBinding<T> { private final WeakReference<Property<T>> propertyref1; private final WeakReference<Property<T>> propertyref2; private boolean updating = false; public void changed(observablevalue<? extends T> sourceproperty, T oldvalue, T newvalue) { if (!updating) { final Property<T> p1 = propertyref1.get(); final Property<T> p2 = propertyref2.get(); if ((p1 == null) (p2 == null)) { if (p1!= null) { p1.removelistener(this); if (p2!= null) { p2.removelistener(this); else { updating = true; if (p1 == sourceproperty) { p2.setvalue(newvalue); else { p1.setvalue(newvalue); updating = false; 23 March 2018 46