Java Design: On the Observer Pattern - Dominik Gruntz

slimwhimperΛογισμικό & κατασκευή λογ/κού

3 Νοε 2013 (πριν από 4 χρόνια και 5 μέρες)

78 εμφανίσεις

Java Design: On the Observer Pattern

1/8
Java Design: On the Observer Pattern
Dominik Gruntz, University of Applied Sciences, Aargau, d.gruntz@fh-aargau.ch


We recently developed a small multi-view editor and made strong use of the observer design
pattern. In particular, we tried to implement auto correction input fields, i.e., text fields which
correct their text while typing. We thereby stumbled over two problems of the observer pattern
(revealed with thrown Java exceptions) which are not often discussed in the pattern literature:
cyclic dependencies and update causality. In this article, we discuss these problems and discuss
implementations of the auto correction input field.

The Observer Pattern
The observer design pattern is used to assure consistency between objects. It is a form of a one-to-
many relationship between a publisher of an event and many subscribers that wish to receive event
notifications. The subscriber objects implement an observer interface that is known by the subject and
register it with the subject. The subject notifies all registered subscribers by calling one of the methods
in the observer interface whenever an event occurs. Simple interfaces for both the subject and the
observer are shown in Listing 1. The subject calls its notify operation whenever its state changes. The
notify method calls the update method of all registered observers, which may call back to the subject
and read the subject's state. The observer pattern is used in Swing to implement the communication
between the model and the views of Swing components; and it is also the design pattern underlying
event notifications in JavaBeans.

interface Subject { interface Observer {
void addObserver(Observer observer); void update();
void removeObserver(Observer observer); }
void notify();
}
Listing 1: Subject and Observer interface

Although this pattern is heavily discussed in many books on software design [Gam95, Gra98], two
aspects are usually not mentioned: cyclic dependencies and causality of event notifications. In a
project with a complicated Swing user interface we stumbled over both problems and run into Java
exceptions. The goal of this paper is to discuss these two problems and to give design hints.
Cyclic Dependencies
With the observer design pattern it is rather easy to introduce hidden cycles into a software system. In
our example we defined a TextField with a corresponding observer implementing the TextListener
interface. Upon an text value changed event, the observer set the value of a model representing a color
in our example. This state change of the model implied that all registered model observers were
notified. One model listener implementing the ColorListener interface was registered to adjust the
content of the text field with the setText method. The invocation of this method implied a call of the
textValueChanged method of the text field listener, which set the value of the color model, and so on.
This cyclic structure is shown in Figure 1, and the calling sequence which lead to an infinite recursion
is shown in the sequence diagram in Figure 2.

:TextField
:TextListener
:ColorListener
:ColorModel

Figure 1: A cyclic dependency between two subjects and its observers

Java Design: On the Observer Pattern

2/8
TextField
TextListener
ColorModel
ColorListener
1: setColor
1.1.1.1.1.1.1.1.1: setText
1.1.1.1: setText
1.1.1.1.1.1.1.1: update
1.1.1.1.1.1.1: notify
1.1.1: update
1.1: : notify
1.1.1.1.1.1: setColor
1.1.1.1.1.1.1.1.1.1: textValueChanged
1.1.1.1.1: textValueChanged

Figure 2: Recursive update sequence

This recursion has to be broken at any of the four live lines, i.e., within an observer implementation
(TextListener or ColorListener) when setting the state of its associated model or within the model itself
(TextField or ColorModel) upon notification of its observers.

For the first solution each listener which calls the setState method (setText or setColor in the above
example) has to compare the old state with the new one. If they are equal, the setState method is not
called. With the second solution the latter comparison is performed directly within the subject. The
subject will only notify its observers if a real state change happens. For self-written models this logic
can directly be added to the setState methods, for existing classes the setState methods have to be
overwritten in a subclass.

setState (State newState){ // in a subclass of TextField
if(!currentState.equals(newState)){ public void setText(String text){
currentState = newState; if(!getText().equals(text)){
notify(); super.setText(text);
} }
} }

The second solution is the preferred one as it frees the implementers of the listeners to take care of the
cyclic dependency problem. A model should notify its listeners only if the state has really changed.

Surprisingly, Java’s text field component does not behave like this. Within the abstract window toolkit
(AWT) any call to the method setText of a TextField results in the notification of all registered listener
(if the peer has been installed), independent of whether the value has changed or not (the notifications
are executed from within the separate AWT event queue thread).

With Swing, the setText method first performs a remove operation followed by an insert operation on
the corresponding document model. Both of these operations are always notified over all document
listeners. However, with Swing instead of an infinite recursion an IllegalStateException is thrown in
the above situation (with the message "attempt to mutate in notification"). This means, that a call of
setText was detected while another call to setText was still active.

Reentrant State Changes
The latter problem we just have mentioned is a special case of a problem which will appear if the
update method of an observer (directly or indirectly) changes the observed subject's state. New event
notifications are then generated while old notifications have not yet been delivered to all observers. A
Java Design: On the Observer Pattern

3/8
consequence of this situation is that different observers may see the subject in different states when
handling the same update notification. Furthermore, the causality of the update notifications is no
longer guaranteed, i.e., an observer may get an update notification issued by an observer state change
before it receives the original notification that triggered this state change. The problem of such call
backs is discussed in the context of the observer pattern in [Szy02].

In Figure 3, a sequence diagram illustrates the problem. A client calls setState1 on a subject and this
implies a notification of all registered observers. The notification corresponding to setState1 is named
update1. The change of the subject's state from within update1 is called setState2 and is notified with
update2. You see that observer observer1 receives the update notifications in the correct order update1
followed by update2, but observer2 receives the update notifications the other way round.

Subject
Client
observer1
Observer
observer2
Observer
1.1.1.1: setState2
1: setState1
1.1.1.1.1.2: update2
1.1.1.1.1.1: update2
1.1.1.1.1: notify
1.1.2: update1
1.1.1: update1
1.1: notify

Figure 3: Sequence diagram of a nested notification

As a concrete application consider a text model which delivers model changed events to its registered
observers. Let the first observer observer1 be an auto-correction (or T9 input converter) observer and
let the second observer be a view (e.g. a text field which adjusts its visual representation only based on
the events it receives). The auto-correction observer replaces a text pattern with another one as text is
typed (I am just working with an editor which offers such a feature and which nerves me from time to
time…). For example, if a closing parenthesis is entered and if this leads to the character sequence
"(c)" then this pattern will be replaced with the copyright symbol "©". Assume that we have the text
"(c Sun microsystems Inc." in the text model and let us add the character ")" at position 2. This model
change is notified by an InsertString(2, ")") event. Observer observer1 removes three characters
starting at position 0 (notified with a Remove(0, 3) event) and then inserts a new character at position 0
(notified with an InsertString(0, "©") event). Now consider the notifications which are received by the
second observer which displays the model in the text field. In the following list the update messages
and the resulting text in the field is displayed:
Java Design: On the Observer Pattern

4/8

Event
Text Field Content
"(c Sun microsystems Inc."
Remove(0, 3 ) "Sun microsystems Inc."
InsertString(0, "©") "©Sun microsystems Inc."
InsertString(2, ")") "©S)un microsystems Inc."

Since the update messages arrived in a different order at observer observer2 as they were initiated in
the model, the resulting value in the text field differs from the model.

There exist several possibilities to resolve this problem:

1. One solution is to enumerate the updates. This would allow the observers to recognize whether an
event is pending and they could delay the handling of an update until all preceding events have
arrived. Pending events would have to be stored in a queue. Although the events are handled in the
proper order, different observers may still see the subject in different states when handling a
particular event. Thus some bookkeeping on the observer side would be necessary.
The events could also be stored in a delivery queue on the subject's side and delivered only when
all preceding events have been delivered to all observers, but observers could still observe different
states on the subject when handling a particular event.

2. Instead of keeping the notification events in a queue, the subject could also delay the execution of
state change operations performed during an event notification and keep them in an operation
queue. As a consequence, all observers would see the subject at the same state upon event
notifications, but we would have asynchronous calls for reentrant state change operations. Clients
would have to be prepared for such an asynchronous calling model.

3. The last and simplest solution is to throw an exception whenever the subject's state is changed
during a notification and leave the problem to the user. This is the solution specified as event
model guideline in the JavaBeans specification. The specification demands, that all listeners must
be notified when an event is dispatched, before any further mutations occur to the source of the
event; otherwise, an IllegalStateException is thrown.

In Swing, the document events (DocumentEvent and UndoableEditEvent) follow this specification.
Thus, it is not possible in Swing to change the content of a text field through its API from within a
listener bound to such a field during an event notification. Thus, in order to implement auto correction
fields, a solution has to be found which avoids reentrant state changes.

Auto Correct Fields: Subclassing Solutions
The first attempt was to subclass JTextField and to overwrite setText with a method that applies the
auto correction rules. This solution works as long as all state change operations go through the text
field's setText method. Editing a text field interactively however uses the underlying document class
directly. This document class allows to register a change listener, but from within such a listener the
value of the text model cannot be changed as we have seen above.

In order to control the value of the document model we have to provide our own implementation
which may be derived using subclassing. However, this approach can only be applied if the class
which implements the Document interface is known. Inspecting the Java sources reveals that the
default model is implemented in class javax.swing.text.PlainDocument.

Within an extension class of PlainDocument we could overwrite the notification routines (fireInsert-
Update, fireChangedUpdate, fireRemoveUpdate). If an auto correction would have to be performed,
the model were changed, otherwise the event would be delivered to the listeners using a super call.
Although this solution works for the auto correction fields, it could not be applied in cases where the
old value of the document model would have to be accessible (e.g. for validating input fields where it
must be possible to revoke invalid operations).

The solution proposed in the API documentation of JTextField is to extend the class PlainDocument
and to overwrite the insertString method. This method is called before the model is changed. Based on
Java Design: On the Observer Pattern

5/8
the arguments which describe the edit operation the document can be modified. Such a solution is
shown in Listing 2.

A drawback of this solution is that we restrict the implementation to work with our document
implementation only. Thus the setDocument operation has to be restricted to support only special
document models. As the setDocument method is called during initialization (with a document of type
PlainDocument even if the createDefaultModel method is overwritten) it cannot simply throw an
UnsupportedOperationException. We use a boolean flag which is set to true after the base class has
been initialized to test whether setDocument is called from a base class constructor.

public class JAutoCorrectField extends JTextField {
private boolean initialized = true;

public JAutoCorrectField(){super();}
public JAutoCorrectField(int columns) {super(columns);}
public JAutoCorrectField(String text) {super(text);}
public JAutoCorrectField(String text, int columns) {super(text, columns);}

protected javax.swing.text.Document createDefaultModel() {
return new AutoCorrectDocument();
}

public void setDocument(javax.swing.text.Document d){
if(!initialized || d instanceof AutoCorrectDocument) // allow calls during initialization of base class
super.setDocument(d);
else
throw new UnsupportedOperationException();
}

private static class AutoCorrectDocument extends PlainDocument {

public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
super.insertString(offset, str, a);
if(str.length() == 1){ // autocorrect only if one letter is inserted
String newValue = getText(0, getLength());
String pat = "(c)";
String rep = "©";
int pos = newValue.lastIndexOf(pat, offset+1);
if(offset+1 - pos == pat.length()){
remove(pos, pat.length());
super.insertString(pos, rep, a);
}
}
}
}
}
Listing 2: Auto correct field using subclassing

Auto Correct Fields: Forwarding Solution
In order to support arbitrary document implementations, forwarding has to be used, i.e., an
implementation of the Document interface has to forward the methods to a contained document
instance. Since Java does not support forwarding in the language directly, all methods in the Document
interface have to be forwarded to the inner document instance. In order to make the code more
readable we implement the document wrapper in the separate class AbstractDocumentWrapper (see
Listing 3).

abstract class AbstractDocumentWrapper implements javax.swing.text.Document {
private javax.swing.text.Document d; // the inner document instance

public AbstractDocumentWrapper (javax.swing.text.Document d){this.d = d; }

public void addDocumentListener(DocumentListener listener){
d.addDocumentListener(listener);}
public void removeDocumentListener(DocumentListener listener){
d.removeDocumentListener(listener);}
public void addUndoableEditListener(UndoableEditListener listener){
d.addUndoableEditListener(listener);}
Java Design: On the Observer Pattern

6/8
public void removeUndoableEditListener(UndoableEditListener listener){
d.removeUndoableEditListener(listener);}
public int getLength(){return d.getLength();}
public String getText(int offset, int length) throws BadLocationException {
return d.getText(offset, length);}
public void getText(int offset, int length, Segment txt) throws BadLocationException{
d.getText(offset, length, txt);}
public Object getProperty(Object key){return d.getProperty(key);}
public void putProperty(Object key, Object value){d.putProperty(key, value);}
public Position getStartPosition(){return d.getStartPosition();}
public Position getEndPosition(){return d.getEndPosition();}
public Position createPosition(int offs) throws BadLocationException{return d.createPosition(offs);}
public Element[] getRootElements(){return d.getRootElements();}
public Element getDefaultRootElement(){return d.getDefaultRootElement();}
public void render(Runnable r){d.render(r);}
public void remove(int offs, int len) throws BadLocationException {d.remove(offs, len); }
public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
d.insertString(offset, str, a);
}
}
Listing 3: Abstract Document Wrapper

We can now define auto correct text fields which work with arbitrary document models. The method
setDocument and the given document model is wrapped with our own implementation. The extension
of the abstract wrapper class is the place where we add our hook which looks similar to the subclassing
solution. However, supercalls are forwarded to the inner document instance in the wrapper class. The
resulting class is shown in Listing 4.

public class JAutoCorrectField extends JTextField {
public JAutoCorrectField(){super();}
public JAutoCorrectField(int columns) {super(columns);}
public JAutoCorrectField(String text) {super(text);}
public JAutoCorrectField(String text, int columns) {super(text, columns);}

public void setDocument(javax.swing.text.Document d){
if(! (d instanceof Document)) d = new Document(d);
super.setDocument(d);
}

private static class Document extends AbstractDocumentWrapper {

Document(javax.swing.text.Document d){super(d);}

public void insertString(int offset, String str, AttributeSet a) throws BadLocationException {
super.insertString(offset, str, a);
if(str.length() == 1){ // autocorrect if one letter is inserted
String newValue = getText(0, getLength());
String pat = "(c)";
String rep = "©";
int pos = newValue.lastIndexOf(pat, offset+1);
if(offset+1 - pos == pat.length()){
remove(pos, pat.length());
super.insertString(pos, rep, a);
}
}
}
}
}
Listing 4: Auto correct field using forwarding

Note, that only changes performed through the document wrapper are validated. If a client has a
reference to the underlying document model, it will be able to change its content without restrictions.

Document Filters in Java 1.4
Sun microsystems has recognized the lack of interaction control of text fields in Swing. Since version
1.4 of the Java 2 platform document filters are available which can be plugged into a document.
Whenever an edit operation is executed on a document, the corresponding method in the document
Java Design: On the Observer Pattern

7/8
filter is invoked. It is the filter’s responsibility to mutate the document. Since Sun did not want to
extend the Document interface, the registration of a document filter is provided in the class
AbstractDocument restricting its use to extensions of this class.

interface DocumentFilter {
void insertString(DocumentFilter.FilterBypass bypass, int offset, String string, AttributeSet attr) ;
void replace(DocumentFilter.FilterBypass bypass, int offset, int length, String text, AttributeSet attr) ;
void remove(DocumentFilter.FilterBypass bypass, int offset, int length)
}

Prior to insertion, removal or replacement of text the corresponding filter method is invoked. The
bypass parameter can be used to perform operations on the document directly, i.e., without going
through the filter again. We can now transform our previous solutions onto this new model. An auto
correction filter implementation for text fields is shown in Listing 5. Although Swing’s JTextField
implementation filters all insert operations with the replace method, we should implement the
insertString method.

JTextField tf = new JTextField();
AbstractDocument doc = (AbstractDocument)tf.getDocument();
doc.setDocumentFilter(
new DocumentFilter(){
public void replace(DocumentFilter.FilterBypass fb,
int offset, int length, String string, AttributeSet attr) throws BadLocationException {
if(length > 0) fb.remove(offset, length);
insertString(fb, offset, string, attr);
}
public void insertString(DocumentFilter.FilterBypass fb,
int offset, String string, AttributeSet attr) throws BadLocationException {
fb.insertString(offset, string, attr);
if(str.length() == 1){
Document d = fb.getDocument();
String text = d.getText(0, d.getLength());
String pat = "(c)";
String rep = "©";
int pos = text.lastIndexOf(pat, offset+1);
if(offset+1 - pos == pat.length()){
fb.remove(pos, pat.length());
fb.insertString(pos, rep, attr);
}
}
}
}
);
Listing 5: Document filter implementation for integer fields

Formatted Text Fields
For the special case of validating input fields, the Swing class JFormattedTextField is provided since
JDK version 1.4. This class relies on instances of java.text.Format to format the content. The control
of the input is performed using document listeners, and for filtering where the selection and the caret
can be placed class NavigationFilter is used. A validating integer input field could be created with the
statements shown in Listing 6.

NumberFormatter formatter = new NumberFormatter(new DecimalFormat("#"));
formatter.setAllowsInvalid(false);
JFormattedTextField ftf = new JFormattedTextField(formatter);
Listing 6: Definition of a JFormattedTextField

Summary
In this article we have presented two aspects of the observer pattern which are both instances of cyclic-
reference-problems. We have discussed how these problems are handled within the Java framework.
For the particular problem of implementing auto correction input fields we have presented solutions
which implement special documents (using subclassing and forwarding) and one based on document
filters. Although new classes to implement formatted input fields are provided since version 1.4, the
Java Design: On the Observer Pattern

8/8
general problem with state change operations performed from within an observer on the observed
subject remains and designers have to be aware of it.

References

[Gam95] E. Gamma, R. Helm, R. Johnson, J. Vlissides, Design Patterns, Addison-Wesley, 1995.

[Gra98] Mark Grand, Patterns in Java, Volume 1, Wiley, 1998.

[Szy02] Clemens Szyperski, with Dominik Gruntz and Stefan Murer, Component Software,
Addison Wesley, 2
nd
edition, 2002.


Dominik Gruntz is professor at the University of Applied Sciences
Aargau in Brugg/Switzerland. His focus is on component software and
framework design.
E-mail: d.gruntz@fh-aargau.ch

Web: http://www.cs.fh-aargau.ch/~gruntz