Osservatore e osservabile

Ecco il problema: stai progettando un programma che renderà i dati che descrivono una scena tridimensionale in due dimensioni. Il programma deve essere modulare e deve consentire viste multiple e simultanee della stessa scena. Ogni vista deve essere in grado di visualizzare la scena da un diverso punto di vista, in diverse condizioni di illuminazione. Ancora più importante, se una qualsiasi parte della scena sottostante cambia, le viste devono aggiornarsi da sole.

Nessuno di questi requisiti rappresenta una sfida di programmazione insormontabile. Se il codice che gestisce ogni requisito dovesse essere scritto de novo , tuttavia, aggiungerebbe un lavoro significativo allo sforzo complessivo. Fortunatamente, il supporto per queste attività è già fornito dalla libreria di classi Java sotto forma di interfaccia Observere classe, Observableentrambe ispirate, in parte, ai requisiti dell'architettura MVC.

L'architettura Model / View / Controller (MVC)

L'architettura Model / View / Controller è stata introdotta come parte di Smalltalk, un popolare linguaggio di programmazione orientato agli oggetti inventato da Alan Kay. MVC è stato progettato per ridurre lo sforzo di programmazione richiesto per creare sistemi che utilizzano più presentazioni sincronizzate degli stessi dati. Le sue caratteristiche principali sono che il modello, i controller e le viste sono trattati come entità separate e che le modifiche apportate al modello dovrebbero riflettersi automaticamente in ciascuna delle viste.

Oltre all'esempio di programma descritto nel paragrafo di apertura sopra, l'architettura Modello / Vista / Controller può essere utilizzata per progetti come i seguenti:

  • Un pacchetto di grafici che contiene visualizzazioni simultanee di grafici a barre, grafici a linee e grafici a torta degli stessi dati.
  • Un sistema CAD, in cui parti del disegno possono essere visualizzate a diversi ingrandimenti, in diverse finestre e in diverse scale.

La figura 1 illustra l'architettura MVC nella sua forma più generale. C'è un modello. Più controller manipolano il modello; più viste visualizzano i dati nel modello e cambiano al variare dello stato del modello.

Figura 1. L'architettura Model / View / Controller

Vantaggi di MVC

L'architettura Model / View / Controller ha diversi vantaggi:

  • Esiste una separazione chiaramente definita tra i componenti di un programma: i problemi in ciascun dominio possono essere risolti indipendentemente.
  • Esiste un'API ben definita: tutto ciò che utilizza correttamente l'API può sostituire il modello, la vista o il controller.
  • Il collegamento tra il modello e la vista è dinamico: si verifica in fase di esecuzione, piuttosto che in fase di compilazione.

Incorporando l'architettura MVC in un progetto, parti di un programma possono essere progettate separatamente (e progettate per svolgere bene il loro lavoro) e quindi unite insieme in fase di esecuzione. Se un componente viene successivamente ritenuto non idoneo, può essere sostituito senza alterare gli altri pezzi. Confronta questo scenario con l'approccio monolitico tipico di molti programmi Java veloci e sporchi. Spesso un frame contiene tutto lo stato, gestisce tutti gli eventi, esegue tutti i calcoli e visualizza il risultato. Pertanto, in tutti tranne il più semplice di tali sistemi, apportare modifiche dopo il fatto non è banale.

Definizione delle parti

Il modello è l'oggetto che rappresenta i dati nel programma. Gestisce i dati e conduce tutte le trasformazioni su quei dati. Il modello non ha una conoscenza specifica né dei suoi controllori né delle sue opinioni - non contiene riferimenti interni a nessuno dei due. Piuttosto, il sistema stesso si assume la responsabilità di mantenere i collegamenti tra il modello e le sue viste e di notificare le viste quando il modello cambia.

La vista è l'oggetto che gestisce la visualizzazione visiva dei dati rappresentati dal modello. Produce la rappresentazione visiva dell'oggetto modello e mostra i dati all'utente. Interagisce con il modello tramite un riferimento all'oggetto del modello stesso.

Il controller è l'oggetto che fornisce i mezzi per l'interazione dell'utente con i dati rappresentati dal modello. Fornisce i mezzi con cui vengono apportate le modifiche, alle informazioni nel modello o all'aspetto della vista. Interagisce con il modello tramite un riferimento all'oggetto del modello stesso.

A questo punto potrebbe essere utile un esempio concreto. Si consideri come esempio il sistema descritto nell'introduzione.

Figura 2. Sistema di visualizzazione tridimensionale

Il pezzo centrale del sistema è il modello della scena tridimensionale. Il modello è una descrizione matematica dei vertici e delle facce che compongono la scena. I dati che descrivono ogni vertice o faccia possono essere modificati (forse come risultato dell'input dell'utente o di una distorsione della scena o di un algoritmo di morphing). Tuttavia, non esiste la nozione di punto di vista, metodo di visualizzazione (wireframe o solido), prospettiva o sorgente luminosa. Il modello è una pura rappresentazione degli elementi che compongono la scena.

La parte del programma che trasforma i dati nel modello in una visualizzazione grafica è la vista. La vista incarna la visualizzazione effettiva della scena. È la rappresentazione grafica della scena da un particolare punto di vista, in particolari condizioni di luce.

Il controller sa cosa può essere fatto al modello e implementa l'interfaccia utente che consente di avviare tale azione. In questo esempio, un pannello di controllo di immissione dati potrebbe consentire all'utente di aggiungere, modificare o eliminare vertici e facce.

Osservatore e osservabile

Il linguaggio Java supporta l'architettura MVC con due classi:

  • Observer: Qualsiasi oggetto che desidera ricevere una notifica quando lo stato di un altro oggetto cambia.
  • Observable: Qualsiasi oggetto il cui stato può essere di interesse e in cui un altro oggetto può registrare un interesse.

Queste due classi possono essere utilizzate per implementare molto di più della semplice architettura MVC. Sono adatti a qualsiasi sistema in cui gli oggetti devono essere automaticamente notificati delle modifiche che si verificano in altri oggetti.

In genere, il modello è un sottotipo di Observablee la vista è un sottotipo di Observer. Queste due classi gestiscono la funzione di notifica automatica di MVC. Forniscono il meccanismo mediante il quale le viste possono essere notificate automaticamente delle modifiche nel modello. I riferimenti agli oggetti al modello sia nel controller che nella vista consentono l'accesso ai dati nel modello.

Osservatore e funzioni osservabili

Di seguito sono elencati i codici per l'osservatore e le funzioni osservabili:

Osservatore

  • public void update(Observable obs, Object obj)

    Chiamato quando si è verificato un cambiamento nello stato dell'osservabile.

Osservabile

  • public void addObserver(Observer obs)

    Aggiunge un osservatore all'elenco interno di osservatori.

  • public void deleteObserver(Observer obs)

    Elimina un osservatore dall'elenco interno degli osservatori.

  • public void deleteObservers()

    Elimina tutti gli osservatori dall'elenco interno degli osservatori.

  • public int countObservers()

    Restituisce il numero di osservatori nell'elenco interno di osservatori.

  • 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 .