Il Pattern "Observer" attraverso un Esempio

Il Pattern "Observer" attraverso un Esempio
G.Morreale

Introduzione:

L'obiettivo di questo articolo è quello di avvicinarsi al design pattern "observer" cercando di scrivere meno teoria a favore di un esempio concreto circa l'uso del pattern.

Ovviamente è d'obbligo una piccola introduzione teorica prima di procedere con l'esempio.

Observer è un design pattern utilizzato quando un oggetto vuole notificare i suoi cambiamenti di stato ad un gruppo di oggetti che dipendono da lui.

Quindi in tale pattern esiste

  • Un oggetto che cambia stato
  • E degli oggetti che vengono notificati

Il primo viene chiamato "subject" o "publisher", infatti è il soggetto del pattern, colui che pubblica i cambiamenti, 
I secondi invece vengono indicati con il nome di "observer","subscriber" o "listener"; sono gli oggetti che osservano i cambiamenti, sottoscrivono una sorta di contratto con il publisher al fine di essere avvisati sui suoi cambiamenti o se vogliamo sono ascoltatori di nuove notifiche.

Come è già facile intuire in questo pattern si crea una relazione uno a molti tra soggetto e listeners.
Tale relazione viene stabilita a run-time.

Nel caso specifico in cui l'observer è uno solo si usa più frequentemente il nome di callback piuttosto che observer.


Esempio

Supponiamo di voler realizzare una piccola applicazione in grado di notificare l'utente attraverso l'apertura di un popup quando arriva una nuova mail sulla sua casella di posta elettronica.


Esisterà una classe che rappresenta il Subject, ovvero il gestore di posta, che in relazione all'arrivo di una nuova mail (cambio di stato) notifica un oggetto grafico (observer) al fine di avvisare l'utente sull'arrivo di una nuova mail.

Cosa è necessario per far funzionare l'esempio ?

Una versione aggiornata di JDK
Le librerie Java MAIL
Un account di posta elettronica in grado di funzionare con il protocollo pop3(o altri supportati da javamail)

nota:
La libreria java mail deve essere nel classpath, nel caso di netbeans è possibile aggiungere la libreria cliccando con il tasto destro sul progetto e accedendo tra le proprietà alla sezione library.

Si consideri di avere due oggetti, uno che si occupa di ricevere la posta, ad ogni arrivo di nuove mail il suo stato interno cambierà; e uno che si occupa di mostrare graficamente il cambio di tale stato.
Al di là del pattern si realizzano in seguito la classe per la ricezione della mail e la classe per la visualizzazione destinata all'utente.

La ricezione della mail

1) Creare un nuovo progetto Java SE
2) Importare le librerie Java Mail
3) Effettuare il copia e incolla della classe MailManager

package esempio;

import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.mail.Folder;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.Store;

/**
 *
 * @author Giuseppe M.
 */
class MailManager implements Runnable
{
    //numero di millisecondi tra un check della cartella inbox ed un altro
    private long interval;    //numero corrente di mail all'interno della cartella. 
    private int total;
    private boolean alive = true; //determina se il thread deve continuare a lavorare o meno.
    private Folder folder; //contiene il riferimento alla cartella INBOX della casella di posta.
    private Store store;//contiene riferimento alla connessione imap

    public MailManager()
    {
        this(2000);
    }

    public MailManager(long millis)
    {        
        interval = millis; //di default ogni due secondi
        initConnection(); //init dell connessione.
        this.total = check();
    }

    /**
     * Inizializza la connessione verso la casella di posta
     */
    private void initConnection()
    {
        try
        {

            String host = "imap.gmail.com";
            String username = "la vostra username";
            String password = "la vostra pwd,.";
            String protocol = "imaps"; //o pop3s
            int count = -1;


            //Otteniamo un oggetto session
            Session session = Session.getInstance(new Properties(), null);

            //Otteniamo lo store
            store = session.getStore(protocol);
            store.connect(host, username, password);

            //Otteniamo la cartella INBOX            
            folder = store.getFolder("INBOX");
            folder.open(Folder.READ_ONLY);
        } catch (MessagingException ex)
        {
            Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
        }

    }

    /**
     * Calcola il numero di mail ogni "interval" millisecondi
     * @return
     */
    public int check()
    {
        // Contiamo i messaggi presenti nella cartella
        int count = -1;
        try
        {
            count = this.folder.getMessageCount();
        } catch (MessagingException ex)
        {
            Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
        }
        return count;
    }

    private void setIntervals(long millis)
    {
        this.interval = millis;
    }

    public void stop()
    {
        this.alive = false;
        
        try
        {//diamo tempo di effettuare l'ultima lettura prima di chiudere le connessioni
            Thread.sleep(interval + 100);
            folder.close(false);
            store.close();
        } catch (InterruptedException ex)
        {
            Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
        } catch (MessagingException ex)
        {
            Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public void run()
    {
        //effettua il calcolo ogni interval millisecondi alla ricerca di nuove mail
        //determina l'arrivo di nuove mail calcolando la differenza con il valore 
        //calcolato al passo precedente
        while (alive)
        {
            int newTot = check();

            if ((newTot - this.total) > 0)
            {
                System.out.println((newTot - this.total) + " Nuove Mail");
                this.total = newTot;
            }

            try
            {
                Thread.sleep(interval);
            } catch (InterruptedException ex)
            {
                Logger.getLogger(MailManager.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    }
}

4) Incollare il seguente codice nel metodo main

    public static void main(String[] args) 
    {
        MailManager pop3 = new MailManager();
        Thread mailThread = new Thread(pop3);
        mailThread.run();
    }

In tal modo a prescindere dal risultato finale dell'esempio, è possibile testare la comunicazione con la propria casella di posta.
Provando a ricevere una nuova mail (ad esempio auto-inviandosela) noterete nello standard output una nuova riga per ogni mail arrivata:

>> 1 Nuove Mail
>> 2 Nuove Mail
..

Il Popup

Si realizzi adesso un componente grafico in grado di comparire in basso a sinistra dello schermo e mostrare un testo di notifica.
Il codice da utilizzare è il seguente:

public class PopUp extends JDialog
{
    private JLabel label; //etichetta sulla quale scrivere il testo
    
    PopUp()
    {
        label = new JLabel("NO MAIL");
        label.setAlignmentX(JLabel.CENTER);
        label.setAlignmentY(JLabel.CENTER);
        label.setBackground(Color.BLACK);
                
        JPanel panel = new JPanel();
        panel.add(label);
        
        panel.setBackground(Color.WHITE);
        this.add(panel);
        
        //otteniamo le dimensioni dello schermo
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();        
        this.setSize(100, 100);       
        this.setLocation( 3,(int)screenSize.getHeight() - this.getHeight() - 35);        
        this.setResizable(false);
        this.setDefaultCloseOperation(PopUp.DISPOSE_ON_CLOSE);
        this.repaint();
    }
}

Per provare le funzionalità del popup aggiungere nel metodo main le seguenti righe di codice:

 PopUp pop = new PopUp();        
 pop.setVisible(true);

L'implementazione del pattern

Adesso è il momento di miscelare il tutto, rispettando i ruoli di ciascun elemento del design pattern.

Si proceda creando la classe astratta "soggetto" e l'interfaccia al fine di raggiungere lo scopo attraverso l'utilizzo del design pattern observer.

La classe soggetto : Subject, viene implementata come classe astratta in modo da unificare l'implementazione dei metodi addListener e removeListener.
E' possibile comunque definirla attraverso un interface e delegare l'implementazione di tutti i metodi alla classe concreta.


nota:
I nomi dei metodi addListener e removeListener sono analoghi a attach, detach o addObserver.
Allo stesso modo la classe Observer è chiamata Listener.

public abstract class Subject
{

    private MailListener listener;

    public void addListener(MailListener l)
    {
        listener = l;
    }

    public void removeListener()
    {
        this.listener = null;
    }

    public abstract void mailNotify();
}

L'interfaccia MailListener, ovvero l'Observer, banalmente sarà implementata con il seguente codice java:

public interface MailListener {

    public void update();
}

Adesso è necessario procedere con l'implementazione delle classi concrete.

La classe MailManager sarà classe concreta del Subject, quindi responsabile dell'implementazione del metodo notify:


    @Override
    public void mailNotify()
    {
        if (this.listeners != null)
        {
            for (MailListener l : this.listeners)
            {
                l.update();
            }
        }
    }


La classe PopUp, invece, necessita di essere notificata circa i cambiamenti di stato del subject quindi dovrà implementare l'interfaccia mailListener al fine di comportarsi di conseguenza.

public class PopUp extends JDialog implements MailListener
{
    private int newMailCount;
    ..
    public PopUp()
    {
        newMailCount = 0;
        ..
        ..
    }    
    public void update()
    {
        label.setText(++newMailCount + " Nuove Mail Nella casella di posta");
        this.setVisible(true);        
    }     
}

Conclusione

Il pattern observer è stato implementato attraverso un esempio concreto e credo interessante.
Nel particolare caso la relazione instaurata tra il subject e l'observer è uno a uno, quindi si è creata una situazione chiamata callback.

L'esempio può comunque essere esteso a più observer, giusto per fare un esempio si potrebbe creare un altro observer che implementa MailListener al fine di inviare un sms(tramite apposito provider) ad un utente per avvisarlo sulla presenza di nuove mail nella casella di posta elettronica.

No comments: