Example 1: Contracts vs Observer

 

This example is a first, simple introduction to coordination contracts. The aims of the case study are the following:

  1. To present a first simple case that illustrates basic ideas and the power of contracts both as a modelling and design primitive when applied to a more "technical" rather than business-oriented example: how contracts can provide an alternative and more "dynamic" solution to the Observer pattern.

  2. To show how contracts can be used to evolve an existing application.

  3. To discuss some design issues associated with contracts in the Observer case.

  4. To motivate the user to start experimenting with contracts by extending the example and building new ones.
The user should have in mind that the example and exercises presented in this section are very simple, pedagogical ones, and by no means illustrate the possible complexity of a real case in which the Observer pattern is used. The aim, however, is to help you, the reader, to become familiar with the approach so that you can apply the principles to more complicated cases in which you want a functionality similar to the one offered by the Observer Pattern.


Introduction: The Observer Pattern

The observer pattern and its variations [1] are very widely used in various classes of applications. A very common case is in graphical-user interfaces in order to separate the presentational aspects of the UI from the underlying data (model-view architecture). The intent of the pattern is to define a one to many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically. The basic structure of the pattern is the following:


In the diagram above, ConcreteSubject is the observable object that stores the state of interest to ConcreteObserver objects. ConcreteSubject sends a notification to its observers when its state changes. An interface for managing the ConcreteSubject’s list of observers is provided in Subject. ConcreteObservers are registered to the list (either by an external controller or themselves), maintain a reference to the ConcreteSubject object and implement the Observers updating interface to keep their state consistent with Subject. Consider the folowing very simpe case of a Java implementation of the above Pattern:


Listing 1. Value.java, the ConcreteObservable

/*
** Value.java
**
** ConcreteObserver class for the SlideValue example
**
*/

import java.util.Observable;

class Value extends Observable {
    private float value;

    public void setValue(float _value) {
        setChanged();
        value = _value;
        notifyObservers();
        clearChanged();
    }

    public float getValue() {
        return value;
    }
}


where the Subject class of Figure 1 is the Java class Observable whose interface is shown in Listing 2 below:

Listing 2. The definition of class Observable.
package java.util;

public class Observable {
    public synchronized void addObserver(Observer o);
    public synchronized void deleteObserver(Observer o);
    public synchronized int countObservers();
    public void notifyObservers();
    public synchronized void notifyObservers(Object arg);
    public synchronized void deleteObservers();
    protected synchronized void setChanged();
    protected synchronized void clearChanged();
    protected synchronized boolean hasChanged();
}

Let us now consider the part of the Observer pattern that concerns classes Observer and ConcreteObserver. Consider the following definition of a class the specifies a ConcreteObserver which actually "observes" instances of class Value above.


Listing 3. TextFieldView.java, the ConcreteObserver for the Application example.
/*
** TextFieldView.java
**
** A ConcreteObserver
**
*/

import java.awt.TextField;
import java.util.Observer;
import java.util.Observable;


class TextFieldView extends TextField implements Observer {

    private Value value;

    public void update(Observable o, Object arg) {
       if(o instanceof Value){
        value = (Value) o;
        setText(Float.toString(value.getValue()));
       }
    }
}

The class specifies a ConcreteObserver which represents a simple text field view, TextFieldView, to the data (Value). TextFieldView implements the Java interface Observer that defines the update() method which every Observer has to implement. update() is triggered by notifyObservers() whenever there is a setValue() in class Value . Method update() simply displays the value of Value in a text field. In other words, it is a simple implementation of the Observer structure shown in Figure 1 above. Let us now consider a new view of the Value. The new view is a subclass of Panel, and, of course, it implements Observer. It also defines a method paint() used for painting. We will not discuss in detail this class since the issue is not its functionality. It is just a view, it could be any other view class instead of PanelView. The class code is in Listing 4 below.


Listing 4. The PanelView class.
/*
** PanelView.java
**
** Another ConcreteObserver for the Application example
**
*/

import java.awt.Panel;
import java.awt.Graphics;
import java.awt.Dimension;
import java.awt.FontMetrics;
import java.awt.Color;
import java.util.Observer;
import java.util.Observable;



class   PanelView extends Panel implements Observer {

    private Value value;

    private int lastPercentage;

    public void setlastPercentage( int _lastPercentage){lastPercentage = _lastPercentage;}

    public int getlastPercentage( ){ return lastPercentage ;}

    public void update(Observable o, Object arg) {

       if(o instanceof Value){
        Graphics  g = getGraphics();
             value = (Value)o;

        setlastPercentage((int)value.getValue());

        repaint();

       }
    }

    public void paint(Graphics g) {
        Dimension    d = size();
        FontMetrics  fm = g.getFontMetrics();
        int          textHeight = fm.getAscent() + fm.getDescent();
        StringBuffer s = new StringBuffer();

        s.append(getlastPercentage());
        s.append("%");

        g.setColor(Color.cyan);

        g.fillRect(0, 0,
            (int)(d.width * (getlastPercentage() / 100.0)),
            d.height);

        g.setColor(Color.black);
        g.drawString(s.toString(),
            (int)((d.width - fm.stringWidth(s.toString()))/2),
            (int)((d.height - textHeight)/2 + fm.getAscent()));
    }
}

To put everything together we need another class. The Controller. The controller for Application is embedded directly in an applet class. In general, the controller should have its own class also. In this case, however, we are primarily interested in the interaction between Value and Views. Therefore, embedding the controller in the applet class leads to a simpler application in which this interaction is easier to understand. (See Listing 5.)


Listing 5. Application applet class.
/*
** Application.java
**
** Applet container for Application example
**
*/

import java.applet.*;
import java.awt.*;
import javax.swing.JSlider;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;

public class Application extends Applet {
    private Value     value;
    private TextFieldView      textFieldView;
    private PanelView panelView;
    private JSlider controller;

    public void init() {
        // Set up a layout manager
        setLayout(new BorderLayout());
        resize(200, 55);

        // Create the value, views, and controller.
        value = new Value();
        textFieldView = new TextFieldView();
        panelView = new PanelView();
        controller = new JSlider(JSlider.HORIZONTAL);

        //create a controller listener
        SliderListener listener=new SliderListener();
         controller.addChangeListener(listener);

        // Add the views to the model's notification list.
        value.addObserver(textFieldView);
        value.addObserver(panelView);
        // Put the views and the controller into the UI.
        add("North", controller);
        add("South", textFieldView);
        add("Center", panelView);
    }

    class SliderListener implements ChangeListener {
     public void stateChanged(ChangeEvent e){
        if (e.getSource() instanceof JSlider)
            value.setValue((float)controller.getValue());
     }

    }
}

This simple applet just creates the components and defines the relationships. ConcreteObserver and ConcreteObservable are are both created by the applet itself, and the Observer is added to the notify list right here. In general, the applet itself does not need to create all of the relationships. You can even have the constructor for the Observer take an Observable as a parameter, although you will usually not want to introduce another class dependency. By constructing the relationship outside of the two classes being related, you reduce the coupling between those classes and allow them to be related in different ways as the occasions arise.


An alternative solution

Based on our experience, the Observer pattern is a very good and robust solution when applied as a design technique to software construction. However, when it comes to evolution, flexibility and code reuse, there are three main problems associated with the previous pattern:

  1. The first problem is that there is a coupling between Observables and Observers. Although the coupling is abstract and minimal in the sense that Subject does not know any concrete Observer, Subject still knows that it is actually observed. It has an interface for attaching observers and a communication protocol with them (notifyObservers). On the other hand, observers not only know that they observe, but, even worse, also whom they observe (they know the concrete Subject). Still, they also have to obey the communication protocol (update() method). Even if this approach leads to a robust design that can be successfully reused, in terms of actual conceptual modelling it is not a natural design, it does not promote component reuse, and can be problematic when dealing with software evolution.

    Let us explain why: Consider the method setValue() in class Value. Naturally, what this method is supposed to do is just to set the value. However, in the case above, it does more than that: it also sends notifications to the Observers, a functionality clearly not natural for a method such as setValue(). Consider now the class Value. It declares itself as being observable. Is this natural? Perhaps, if ConcreteSubject was of a kind that we can assume will always be observed, for instance a thermometer, then perhaps constructing ConcreteSubject as observable is a natural design. But what if it is that case that ConcreteObservable is something like a Stock class in a stock-trading system? Is it always observable? If it is constructed as observable why having all this extra code when it is not? Even worse, if it is coded as not observable, how we can make it Observable? Clearly, by modifying the code. In other words, add all these statements that are necessary to implement an Observable class. But are such modifications desirable from the evolution point of view? The answer is clearly no. Consider now the observers. Is it natural for them to know that they observe and whom they observe? Is it natural for the class TextFieldView above, that is supposed to draw, to actually know by default not only how but also what it draws? What if we wanted to reuse it for drawing something different, not related to Value? The point we wish to make is this: Even if there are better designs to deal with some of these issues, for instance an Observer with a manager (mediator) (see [1] ), they can not be so dynamic to support code reuse and software evolution without having to make intrusive code modifications. The reason is simple: we can not predict evolution at design time and therefore we need a methodology and not just good designs in order to deal with the dynamic nature of software evolution.


  2. The second problem is one that is clearly putting more emphasis on the last remark above. It is often the case that for new requirements (for instance more than one observations of state on ConcreteSubject) another communication protocol is needed. But how can we predict that at design time? And when this situation arises how easy is it to find the Observable and all its Observers and change all this code to modify the protocol between them? And then how we guarantee that we modify correctly and, even worse, without triggering changes elsewhere in the system?

  3. The third problem is that changing the functionality of observers (different observation actions) requires either new methods added or subclassing. In other words, with known approaches it is not possible for different instances of the same Observer class to provide different functionalities. For instance, in the case above, how is it feasible for an object of PanelView to paint using one font and another object to paint using another font? Clearly by either subclassing PanelView or by adding another paint() operation. But there are two problems with these solutions. In the first case we make a static adaptation of PanelView. Subclassing is static because a future removal from the domain or modification of the asset that we wished to model by subclassing, can not be performed so easily without triggering changes to other parts in which the (sub)class participated. The second case is often even worse. Firstly, adding an interface is not always possible. But even when it is, it is very likely to be intrusive not only to the components in which it is added but also to other parts of the system.

Often, the problems mentioned above can be eliminated and the observer functionality can be provided, in a very easy, dynamic, and non-intrusive way, by a contracts approach. The general schema is the following:






We still use the term Observable and Observer just to make a direct analogy to the classes above, but in fact there are no more Observable and Observer. Actually, with the contracts approach, all the code in orange in the above classes in not needed. In other words, the components should originally being built without the code in orange. The components provide what they are supposed to provide. There is no Observable class, no Observer interface, no notifyObservers(), no update(), no registration of observers, no classes that observe and know what they observe. It is the contracts that link the Subject (Observable) to other objects that are interested in changes of the Subject state. In other words, any instance of any class can either be an Observable or an Observer depending on the kind of contracts you plug on top of it. Moreover, contracts are not mediators. Actually, Subject and Observers do not even know that contracts exist. Contracts are plugged on top of them, providing the ability to coordinate and superpose the behaviour of the Observers based on the state changes on the Subject. Consider the following contract, that can detect changes on the state of Value and coordinate the behaviour of TextFieldView.


Listing 6. ValueTextFieldViewContract between Value and TextFieldView.

contract ValueTextFieldViewContract
participants
    // contract participants
        value:Value;
        view:TextFieldView;
coordination
    // rules
    TextFieldObservation: when *->> value.setValue(_value)
                          after{
                                view.setText(Float.toString(value.getValue()));
                          };
end contract //ValueTextFieldViewContract




Note that in the initial classes Value and TextFieldView above, all code in orange is not needed and is commented. In other words, for the contracts to provide the desired functionality, this code is not necessary. Let us now consider the ValueTextFieldViewContract. The contract has as participants instances of Value and TextFieldView. The Contract specifies an observation rule, stating that whenever a call is made on Value's setValue (i.e the state is changed), then perform setText on view. This is a lot more simpler in terms of modelling, and allows for a lot more natural implementation of Value and TextFieldView. Consider that setValue does only what is supposed to do: setting the value. 
OK, there is much more to say and there may be a lot of questions of the form : "What if we had this case ...?" Be patient and we will try to guess and answer these questions later on. We still have PanelView to consider and therefore we have to discuss how contracts can coordinate and superpose behavior on that view. Consider the following contract, ValuePanelViewContract1:


Listing 7. ValuePanelViewContract1 between Value and PanelView.

contract ValuePanelViewContract1
participants
    // contract participants
    value:Value;
    view:PanelView;
coordination
    // rules
    PanelViewObservation: when *->> value.setValue(_value)
                          after{
                                view.setlastPercentage((int)value.getValue());
                                view.repaint(); 
                          };
end contract //ValuePanelViewContract1




Classes Value and PanelView are commented similarly to the previous case. Contract ValuePanelViewContract1 has a similar functionality to the above case. It coordinates the instances of PanelView class in order to provide the desired functionality without using the Observer pattern. But is there anything more that contracts can actually do as we promised earlier? A first answer is in contract,ValuePanelViewContract2, whose specification is shown in the picture below:

Listing 8. ValuePanelViewContract2 between Value and PanelView.

contract ValuePanelViewContract2
participants
   
// contract participants
    value:Value;
    view:PanelView;
attributes int min=15; int max=85;
operations
    void paintInColor(Graphics g,int _lastPercentage, Color c1, Color c2) {

        Dimension d = view.getSize();
        FontMetrics fm = g.getFontMetrics();
        int textHeight = fm.getAscent() + fm.getDescent();
        StringBuffer s = new StringBuffer();

        s.append(view.getlastPercentage());
        s.append("%");

        g.setColor(c1);

        g.fillRect(0, 0,
                (int)(d.width * (view.getlastPercentage() / 100.0)),
                  d.height);

        g.setColor(c2);
        g.drawString(s.toString(),
                (int)((d.width - fm.stringWidth(s.toString()))/2),
                (int)((d.height - textHeight)/2 + fm.getAscent()));
   }
coordination
    // rules
    PanelViewSuperposition2A: when *->> view.paint(g)&& (value.getValue()>max)
                             do{
                               Graphics gr=view.getGraphics();
                               Font font=new Font("Serif",Font.BOLD,28);
                               gr.setFont(font);
                               paintInColor(gr,(int)value.getValue(),Color.red,Color.blue);

                             };
    PanelViewSuperposition2B: when *->> view.paint(g)&& (value.getValue()<min)
                             do{
                              Graphics gr=view.getGraphics();
                              Font font=new Font("Serif",Font.BOLD,28);
                              gr.setFont(font);
                              paintInColor(gr,(int)value.getValue(),Color.green,Color.black);
                             };
end contract //ValuePanelViewContract2




The contract has as participants instances of Value and PanelView and defines two rules. These rules, PanelViewSuperposition3 and PanelViewSuperposition4, superpose behaviour on the paint operation of PanelView, without having to modify the source code of paint. Actually, what the contract does is detecting the call on paint() and replacing the execution of paint() with code that sets a new font and an alternative operation paintInColor() defined in the contract. It is worthwhile to notice the trigger conditions in the two rules, specified using &&. These conditions declare when the rule is active. In the case above, the first rule is active only when value is more than a max and the second only when value is less than min, where min and max are specified in the contract. In any other case, the rules are inactive and when a call to paint() is performed, paint() is actually executed. In other words, the contract superposes the way PanelView is going to paint. If rules are active, PanelView paints according to the contract rules, i.e with color, otherwise the original paint() is performed. It is important to note that the specification of replacement operations such as paintInColor() above should adhere to whatever specifications the original operation may had (pre-post conditions)(Meyer's design by contract). Contracts such as the one above can be dynamically added and deleted to the components (in this case to Value and PanelView) so that new desired behaviour can emerge to deal with new requirements (evolution). Notice that it is even possible to have different instances of PanelView to paint differently by just plugging different contracts on top of these instances. In other words, functionalities such as the one described in problem 3 above can be implemented using contracts very easily and without modifying or adapting the components (i.e, subclassing or adding new interfaces). System behaviour and evolution is just a matter of configuration i.e, what contracts you establish/not establish between the participants. For instance, we can delete the above contract and return to the initial functionality or delete this and add another one with different rules to achieve new functionality. There is an exercise on that at the end of this document. The basic configuration of the above example is the following adapted version of the class Application in the Observer example. The Application class with contracts instaed of Observers is similar to the one defined earlier with just the following lines of code added and with the orange lines deleted.

Listing 9. The parts added/deleted to the Application.

ValueTextFieldViewContract ct=new ValueTextFieldViewContract(value,textFieldView);
ValuePanelViewContract1 ct1=new ValuePanelViewContract1(value,panelView );


// Add the views to the model's notification list.
value.addObserver(textFieldView);
value.addObserver(panelView);



The sources for the example above are:
Value.java TextFieldView.java PanelView.java ContractsApplication.java ValueTextFieldViewContract.ctr ValuePanelViewContract1.ctr ValuePanelViewContract2.ctr



Some issues/questions

1. What if views also update the model?

In cases where views also update the state of the model you can still use contracts for the modelling and implementation. However, in such cases, you should be careful not to write contracts with rules that have, as triggers, operation calls and, at the same time, perform actions on the view that call back the trigger operation. 

2. What if the operation that changes the state of Subject is private (internal to Subject)?

In this case, you can not use contracts to intercept calls to such operations and coordinate the components that are interested in the state of the model. This is because, with the current micro-architecture and the generated code that implements them, operations under potential coordination are declared in the SubjectInterface of the Component part of the contracts pattern. Therefore, in order to use contracts instead of Observer, with all the advantages discussed above, operations such as setValue() should be public. Alternatively, you may use state condition rules to detect changes of state and perform the necessary actions.



Exercises/examples you may try yourself

Exercise 1.


Exercise 2.
Exercise 3.

For answers, please visit www.atxsoftware.com/CDE

 

References

  1. E. Gamma, R.Helm, R. Johnson and J. Vlissidis. Design Patterns: Elements of Reusable Object Oriented Software, Addison Wesley, 1995.

 



 © Copyright 2001 ATX Software SA. All rights reserved.