Observateur et observable

Voici le problème: vous concevez un programme qui rendra des données décrivant une scène tridimensionnelle en deux dimensions. Le programme doit être modulaire et permettre plusieurs vues simultanées de la même scène. Chaque vue doit pouvoir afficher la scène à partir d'un point de vue différent, dans des conditions d'éclairage différentes. Plus important encore, si une partie de la scène sous-jacente change, les vues doivent se mettre à jour.

Aucune de ces exigences ne présente un défi de programmation insurmontable. Cependant, si le code qui gère chaque exigence devait être écrit de novo , cela ajouterait un travail important à l'effort global. Heureusement, la prise en charge de ces tâches est déjà fournie par la bibliothèque de classes Java sous la forme d'interface Observeret de classe - Observabletoutes deux inspirées, en partie, des exigences de l'architecture MVC.

L'architecture modèle / vue / contrôleur (MVC)

L'architecture Model / View / Controller a été introduite dans le cadre de Smalltalk, un langage de programmation orienté objet populaire inventé par Alan Kay. MVC a été conçu pour réduire l'effort de programmation requis pour construire des systèmes utilisant plusieurs présentations synchronisées des mêmes données. Ses principales caractéristiques sont que le modèle, les contrôleurs et les vues sont traités comme des entités distinctes et que les modifications apportées au modèle doivent être reflétées automatiquement dans chacune des vues.

En plus de l'exemple de programme décrit dans le paragraphe d'ouverture ci-dessus, l'architecture Modèle / Vue / Contrôleur peut être utilisée pour des projets tels que les suivants:

  • Un package graphique qui contient des vues simultanées de graphique à barres, de graphique linéaire et de graphique à secteurs des mêmes données.
  • Un système CAO, dans lequel des parties de la conception peuvent être visualisées à différents agrandissements, dans différentes fenêtres et à différentes échelles.

La figure 1 illustre l'architecture MVC dans sa forme la plus générale. Il y a un modèle. Plusieurs contrôleurs manipulent le modèle; plusieurs vues affichent les données dans le modèle et changent à mesure que l'état du modèle change.

Figure 1. L'architecture modèle / vue / contrôleur

Avantages de MVC

L'architecture Modèle / Vue / Contrôleur présente plusieurs avantages:

  • Il existe une séparation clairement définie entre les composants d'un programme - les problèmes dans chaque domaine peuvent être résolus indépendamment.
  • Il existe une API bien définie - tout ce qui utilise correctement l'API peut remplacer le modèle, la vue ou le contrôleur.
  • La liaison entre le modèle et la vue est dynamique - elle se produit au moment de l'exécution plutôt qu'au moment de la compilation.

En incorporant l'architecture MVC dans une conception, des éléments d'un programme peuvent être conçus séparément (et conçus pour bien faire leur travail), puis liés ensemble au moment de l'exécution. Si un composant est jugé par la suite inapproprié, il peut être remplacé sans affecter les autres pièces. Comparez ce scénario avec l'approche monolithique typique de nombreux programmes Java rapides et sales. Souvent, un cadre contient tout l'état, gère tous les événements, effectue tous les calculs et affiche le résultat. Ainsi, dans tous les systèmes sauf le plus simple, apporter des modifications après coup n'est pas anodin.

Définition des pièces

Le modèle est l'objet qui représente les données dans le programme. Il gère les données et effectue toutes les transformations sur ces données. Le modèle n'a aucune connaissance spécifique de ses contrôleurs ou de ses vues - il ne contient aucune référence interne à l'un ou l'autre. Au contraire, le système lui-même assume la responsabilité de maintenir les liens entre le modèle et ses vues et de notifier les vues lorsque le modèle change.

La vue est l'objet qui gère l'affichage visuel des données représentées par le modèle. Il produit la représentation visuelle de l'objet modèle et affiche les données à l'utilisateur. Il interagit avec le modèle via une référence à l'objet modèle lui-même.

Le contrôleur est l'objet qui fournit les moyens d'interaction de l'utilisateur avec les données représentées par le modèle. Il fournit les moyens par lesquels des modifications sont apportées, soit aux informations du modèle, soit à l'apparence de la vue. Il interagit avec le modèle via une référence à l'objet modèle lui-même.

À ce stade, un exemple concret pourrait être utile. Prenons comme exemple le système décrit dans l'introduction.

Figure 2. Système de visualisation en trois dimensions

L'élément central du système est le modèle de la scène tridimensionnelle. Le modèle est une description mathématique des sommets et des faces qui composent la scène. Les données décrivant chaque sommet ou face peuvent être modifiées (peut-être à la suite d'une entrée utilisateur ou d'un algorithme de distorsion ou de morphing de scène). Cependant, il n'y a aucune notion de point de vue, de méthode d'affichage (filaire ou solide), de perspective ou de source lumineuse. Le modèle est une pure représentation des éléments qui composent la scène.

La partie du programme qui transforme les données du modèle en un affichage graphique est la vue. La vue incarne l'affichage réel de la scène. C'est la représentation graphique de la scène d'un point de vue particulier, dans des conditions d'éclairage particulières.

Le contrôleur sait ce qui peut être fait au modèle et implémente l'interface utilisateur qui permet d'initier cette action. Dans cet exemple, un panneau de contrôle de saisie de données peut permettre à l'utilisateur d'ajouter, de modifier ou de supprimer des sommets et des faces.

Observateur et observable

Le langage Java prend en charge l'architecture MVC avec deux classes:

  • Observer: Tout objet qui souhaite être notifié lorsque l'état d'un autre objet change.
  • Observable: Tout objet dont l'état peut présenter un intérêt, et dans lequel un autre objet peut enregistrer un intérêt.

Ces deux classes peuvent être utilisées pour implémenter bien plus que la simple architecture MVC. Ils conviennent à tout système dans lequel les objets doivent être automatiquement notifiés des modifications qui se produisent dans d'autres objets.

En règle générale, le modèle est un sous-type de Observableet la vue est un sous-type de Observer. Ces deux classes gèrent la fonction de notification automatique de MVC. Ils fournissent le mécanisme par lequel les vues peuvent être automatiquement notifiées des changements dans le modèle. Les références d'objet au modèle à la fois dans le contrôleur et dans la vue permettent d'accéder aux données du modèle.

Fonctions d'observateur et d'observable

Voici des listes de codes pour les fonctions d'observateur et observables:

Observateur

  • public void update(Observable obs, Object obj)

    Appelé lorsqu'un changement s'est produit dans l'état de l'observable.

Observable

  • public void addObserver(Observer obs)

    Ajoute un observateur à la liste interne des observateurs.

  • public void deleteObserver(Observer obs)

    Supprime un observateur de la liste interne des observateurs.

  • public void deleteObservers()

    Supprime tous les observateurs de la liste interne des observateurs.

  • public int countObservers()

    Renvoie le nombre d'observateurs dans la liste interne des observateurs.

  • protected void setChanged()

    Sets the internal flag that indicates this observable has changed state.

  • protected void clearChanged()

    Clears the internal flag that indicates this observable has changed state.

  • public boolean hasChanged()

    Returns the boolean value true if this observable has changed state.

  • public void notifyObservers()

    Checks the internal flag to see if the observable has changed state and notifies all observers.

  • public void notifyObservers(Object obj)

    Checks the internal flag to see if the observable has changed state and notifies all observers. Passes the object specified in the parameter list to the notify() method of the observer.

Next we'll take a look at how to create a new Observable and Observer class, and how to tie the two together.

Extend an observable

A new class of observable objects is created by extending class Observable. Because class Observable already implements all of the methods necessary to provide the desired behavior, the derived class need only provide some mechanism for adjusting and accessing the internal state of the observable object.

In the ObservableValue listing below, the internal state of the model is captured by the integer n. This value is accessed (and, more importantly, modified) only through public accessors. If the value is changed, the observable object invokes its own setChanged() method to indicate that the state of the model has changed. It then invokes its own notifyObservers() method in order to update all of the registered observers.

Listing 1. ObservableValue

 import java.util.Observable; public class ObservableValue extends Observable { private int n = 0; public ObservableValue(int n) { this.n = n; } public void setValue(int n) { this.n = n; setChanged(); notifyObservers(); } public int getValue() { return n; } } 

Implement an observer

A new class of objects that observe the changes in state of another object is created by implementing the Observer interface. The Observer interface requires that an update() method be provided in the new class. The update() method is called whenever the observable changes state and announces this fact by calling its notifyObservers() method. The observer should then interrogate the observable object to determine its new state, and, in the case of the MVC architecture, adjust its view appropriately.

In the following TextObserver listing, the notify() method first checks to ensure that the observable that has announced an update is the observable that this observer is observing. If it is, it then reads the observable's state, and prints the new value.

Listing 2. TextObserver

 import java.util.Observer; import java.util.Observable; public class TextObserver implements Observer { private ObservableValue ov = null; public TextObserver(ObservableValue ov) { this.ov = ov; } public void update(Observable obs, Object obj) { if (obs == ov) { System.out.println(ov.getValue()); } } } 

Tie the two together

A program notifies an observable object that an observer wishes to be notified about changes in its state by calling the observable object's addObserver() method. The addObserver() method adds the observer to the internal list of observers that should be notified if the state of the observable changes.

The example below, showing class Main, demonstrates how to use the addObserver() method to add an instance of the TextObserver class (Listing 2) to the observable list maintained by the ObservableValue class (Listing 1).

Listing 3. addObserver()

 public class Main { public Main() { ObservableValue ov = new ObservableValue(0); TextObserver to = new TextObserver(ov); ov.addObserver(to); } public static void main(String [] args) { Main m = new Main(); } } 

How it all works together

The following sequence of events describes how the interaction between an observable and an observer typically occurs within a program.

  1. First the user manipulates a user interface component representing a controller. The controller makes a change to the model via a public accessor method -- which is setValue() in the example above.
  2. The public accessor method modifies the private data, adjusts the internal state of the model, and calls its setChanged() method to indicate that its state has changed. It then calls notifyObservers() to notify the observers that it has changed. The call to notifyObservers() could also be performed elsewhere, such as in an update loop running in another thread.
  3. The update() methods on each of the observers are called, indicating that a change in state has occurred. The observers access the model's data via the model's public accessor methods and update their respective views.

Observer/Observable in an MVC architecture

Now let's consider an example demonstrating how observables and observers typically work together in an MVC architecture. Like the model in the ObservableValue (Listing 1) the model in this example is very simple. Its internal state consists of a single integer value. The state is manipulated exclusively via accessor methods like those in ObservableValue. The code for the model is found here.

Initially, a simple text view/controller class was written. The class combines the features of both a view (it textually displays the value of the current state of the model) and a controller (it allows the user to enter a new value for the state of the model). The code is found here.

By designing the system using the MVC architecture (rather than embedding the code for the model, the view, and the text controller in one monolithic class), the system is easily redesigned to handle another view and another controller. In this case, a slider view/controller class was written. The position of the slider represents the value of the current state of the model and can be adjusted by the user to set a new value for the state of the model. The code is found here.

About the author

Todd Sundsted has been writing programs since computers became available in desktop models. Though originally interested in building distributed object applications in C++, Todd moved to the Java programming language when Java became the obvious choice for that sort of thing.

This story, "Observer and Observable" was originally published by JavaWorld .