versione 31/3/2016 - prec. 13/09/2015

Introduzione ai Pattern

Prima di andare nel dettaglio del pattern Model-View-Controller facciamo una breve introduzione ai Design Patterns. I Pattern vengono divisi in base a 2 criteri: lo scopo e il raggio di azione.

In base allo scopo, i Pattern, si distinguono in 3 gruppi:
1.Creazionali: pongono attenzione al modo in cui gli oggetti devono essere creati
2.Strutturali: si riferiscono al modo in cui le classi e gli oggetti vengono composti
3.Comportamentali: si concentrano sulle interazioni che avvengono tra classi e oggetti

Mentre invece in base al raggio di azione si distinguono in 2 gruppi:
1.Classi: si concentrano sulla relazione di ereditarietà delle classi, definita a compile-time quindi statica
2.Oggetti: si focalizzano sulla relazione tra gli oggetti, definita a run-time quindi dinamica

Ecco come si presenta la loro classificazione nel celebre libro “Design Pattern” scritto dalla “Gang of Four” (Gamma, Helm, Johnson e Vlissides).

Analizzeremo i pattern che servono al nostro scopo: Pattern Strategy e Pattern Observer.

Pattern Strategy

Lo strategy pattern è uno dei pattern comportamentali. L'obiettivo di questa architettura è isolare un algoritmo all'interno di un oggetto. Il pattern strategy è utile in quelle situazioni dove sia necessario modificare dinamicamente gli algoritmi utilizzati da un'applicazione.  Il client definisce l’algoritmo da utilizzare, incapsulandolo in un contesto, il quale verrà utilizzato nella fase di elaborazione. Il contesto detiene i puntamenti alle informazioni necessarie al fine della elaborazione, cioè dati e funzionalità.

Questo pattern prevede che gli algoritmi siano intercambiabili tra loro (in base ad una qualche condizione) in modo trasparente al client che ne fa uso. In altre parole: la famiglia di algoritmi che implementa una funzionalità (ad esempio ordinamento) esporta sempre la medesima interfaccia, in questo modo il client dell'algoritmo non deve fare nessuna assunzione su quale sia la strategia istanziata in un particolare istante.

Gli algoritmi definiscono il modo in cui vengono elaborate le informazioni. Possiamo creare delle classi di algoritmi che implementano in modo diverso uno stesso algoritmo oppure possiamo creare delle nuove classi di algoritmi. Per esempio nel caso di un problema di ordinamento, possiamo creare diverse classi che implementano l’algoritmo BubbleSorting ma in modo diverso, così come possiamo creare delle classi di ordinamento basate su diversi algoritmi, per esempio SelectionSorting oppure InsertionSorting.

L’utilizzo di questo pattern consente al client di poter scegliere quale algoritmo e quale implementazione utilizzare senza dover conoscere i loro dettagli implementativi. Questo pattern è composto dai seguenti partecipanti:

Strategy: dichiara una interfaccia che verrà invocata dal Context in base all’algoritmo prescelto
ConcreteStrategy: effettua l’overwrite del metodo del Context al fine di ritornare l’implementazione dell’algoritmo prescelta
Context: detiene le informazioni di contesto (dati ed algoritmo da utilizzare) ed ha il compito di invocare l’algoritmo

La classe base richiede una strategia esterna per portare a termine correttamente il suo compito

interface Strategy 
{
   public int doOperation(int num1, int num2);
}

class OperationAdd implements Strategy
{
   @Override
   public int doOperation(int num1, int num2) 
   {
      return num1 + num2;
   }
}

class OperationSubstract implements Strategy
{
   @Override
   public int doOperation(int num1, int num2) 
   {
      return num1 - num2;
   }
}

class OperationMultiply implements Strategy
{
   @Override
   public int doOperation(int num1, int num2) {
      return num1 * num2;
   }
}

class Context 
{
   private Strategy strategy;

   public Context(Strategy strategy)
   {
      this.strategy = strategy;
   }

   public int executeStrategy(int num1, int num2)
   {
      return strategy.doOperation(num1, num2);
   }
}

public class StrategyPatternDemo 
{
   public static void main(String[] args) 
   {
      Context context = new Context(new OperationAdd());		
      System.out.println("10 + 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationSubstract());		
      System.out.println("10 - 5 = " + context.executeStrategy(10, 5));

      context = new Context(new OperationMultiply());		
      System.out.println("10 * 5 = " + context.executeStrategy(10, 5));
   }
}

La cui esecuzione produce il seguente output:

C:\Java>"C:\Program Files\Java\jdk1.7.0_21\bin\javac.exe" StrategyPatternDemo.java
C:\Java>"C:\Program Files\Java\jdk1.7.0_21\bin\java.exe" StrategyPatternDemo
10 + 5 = 15
10 - 5 = 5
10 * 5 = 50

Vediamo come si presenta il pattern in UML dell’esempio appena presentato:

Nella libreira del JDK il pattern Strategy viene utilizzato nelle librerie grafiche (awt e swing) per esempio per definire la tipologia di Border da applicare al JPanel. Il Context è definito da JPanel, in particolare da JComponent, che detiene il puntatore alla “strategia” del bordo da utilizzare.

jPanel.setBorder(new LineBorder(Color.RED));
jPanel.setBorder(new TitledBorder("Hello Border!"));

Pattern Observer

Il pattern Observer risulta utile per gestire dinamicamente l'accoppiamento tra "Sorgenti di eventi" e classi che devono reagire quando questi si verificano. La sua realizzazione prevede una classe "osservabile" (in Java si implementa con la classe Observable) e degli oggetti "osservatori" (in Java sono implementabili con l'interfaccia Observer). Il ruolo di osservatore (subject) è il ruolo svolto da colui che si mostra interessato ai cambiamenti di stato. Il ruolo di Observable (osservato) è il ruolo svolto da colui che viene monitorato.

Questo pattern comportamentale basato su oggetti risulta utile quando si vuole realizzare una dipendenza uno-a-molti dove il cambiamento di stato di un soggetto viene notificato a tutti i soggetti che si sono mostrati interessati.

Questo pattern è composto dai seguenti partecipanti:

- Subject: espone l’interfaccia che consente agli osservatori di iscriversi e cancellarsi; mantiene una reference per tutti gli osservatori iscritti. Il Subject include una lista degli osservatori che si registrano presso il Subject osservato tramite i metodi addObserver() e si cancellano tramite il metodo removeObserver(). Mentre invece il metodo notifyObservers() viene invocato dalla classe concreta ConcreteSubject quando interviene un cambio di stato.

import java.util.ArrayList;
import java.util.List;

public abstract class Subject
{
    private List<Observer> list = new ArrayList<Observer>();

    public void addObserver(Observer observer)
    {
        list.add( observer );
    }

    public void removeObserver(Observer observer)
    {
        list.remove( observer );
    }

    public void notifyObservers()
    {
        for (Observer observer: list)
        {
            observer.update();
        }
    }
}

- Observer: espone l’interfaccia (update) che consente di aggiornare gli osservatori qualora si verifichi un cambio di stato del soggetto osservato. Pertanto quando avvengono delle modifiche nel soggetto osservato, verrà invocato il metodo update()di tutti gli osservatori.

public interface Observer
{
    public void update();
}

- ConcreteSubject: mantiene lo stato del soggetto osservato e notifica gli osservatori in caso di un cambio di stato con (notifyObservers().

public class ConcreteSubject extends Subject
{
    private boolean state;
    public void setState(boolean state)
    {
        this.state = state;
        notifyObservers();
    }
    public boolean getState()
    {
        return this.state;
    }
}

- ConcreteObserver: implementa l’interfaccia dell’Observer definendo il comportamento in caso di cambio di stato del soggetto osservato. Il ConcreteObserver implementa il metodo update() per definire l’azione da intraprendere quando interviene un cambio di stato del Subject.

public class ConcreteObserver implements Observer
{
    @Override
    public void update()
    {
        System.out.println("Sono " + this + ": il Subject e' stato modificato!");
    }
}

Il Subject sa che una lista di Observer sono interessati al suo stato ma non conosce le classi concrete degli Observer, pertanto non vi è un accoppiamento forte tra di loro. Vediamo come si presenta il Pattern Observer utilizzando il Class Diagram in UML:

Creiamo la classe Client che si occupa di creare il soggetto da osservare e due osservatori che si registrano per essere notificati in caso di cambio di stato del soggetto osservato. Successivamente rimuoviamo un osservatore e cambiamo lo stato del soggetto osservato per notare che non riceverà più notifiche.

public class Client
{
    public static void main(String[] args)
    {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer1 = new ConcreteObserver();
        Observer observer2 = new ConcreteObserver();

        //aggiungo 2 observer che saranno notificati
        subject.addObserver(observer1);
        subject.addObserver(observer2);

        //modifico lo stato
        subject.setState( true );

        //rimuovo il primo observer che non sarà + notificato
        subject.removeObserver(observer1);

        //modifico lo stato
        subject.setState( false );
   
}
}

Combinando i precedenti script otteniamo il seguente programma:

import java.util.ArrayList;
import java.util.List;
import java.io.*;

interface Observer 
{
    public void update();
}

abstract class Subject 
{
    private List list = new ArrayList();

    public void addObserver(Observer observer) 
    {
        list.add( observer );
    }

    public void removeObserver(Observer observer) 
    {
        list.remove( observer );
    }

    public void notifyObservers() 
    {
        for (Observer observer: list) 
        {
            observer.update();
        }
    }
}

class ConcreteObserver implements Observer 
{
    @Override
    public void update()
    {
        System.out.println("Sono " + this + ": il Subject e' stato modificato!");
    }
}

class ConcreteSubject extends Subject 
{
    private boolean state;
    public void setState(boolean state) 
    {
        this.state = state;
        notifyObservers();
    }
    public boolean getState() 
    {
        return this.state;
    }
}

public class Client 
{
    public static void main(String[] args) 
    {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer1 = new ConcreteObserver();
        Observer observer2 = new ConcreteObserver();
 
        //aggiungo 2 observer che saranno notificati
        subject.addObserver(observer1);
        subject.addObserver(observer2);
 
        //modifico lo stato
        subject.setState( true );
 
        //rimuovo il primo observer che non sarà + notificato
        subject.removeObserver(observer1);
 
        //modifico lo stato
        subject.setState( false );
 
    }
}

la cui esecuzione produce il seguente output:

C:\Java>"C:\Program Files\Java\jdk1.7.0_21\bin\javac.exe" Client.java
C:\Java>"C:\Program Files\Java\jdk1.7.0_21\bin\java.exe" Client
Sono ConcreteObserver@1befab0: il Subject e' stato modificato!
Sono ConcreteObserver@13c5982: il Subject e' stato modificato!
Sono ConcreteObserver@13c5982: il Subject e' stato modificato!

Il pattern del nostro esempio in UML diventa:

Nelle librerie Java il Subject e l’Observer sono già presenti con le classi java.util.Observable e java.util.Observer per cui non occorre definirle.

Pertanto, per non reinventare la ruota ogni volta, possiamo applicare queste classi all'esempio precedente. Procediamo con il refactoring del nostro codice per adattarlo alle classi java.util.Observable e java.util.Observer.

import java.io.*;
import java.util.Observable;
import java.util.Observer;
 
class ConcreteObserver implements Observer 
{
    @Override
    public void update(Observable o, Object arg) 
    {
        System.out.println("Sono " + this + ": il Subject e stato modificato!");
    }
}

class ConcreteSubject extends Observable 
{
    private boolean state;
    public void setState(boolean state) 
    {
        this.state = state;
        setChanged();
        notifyObservers();
    }
    public boolean getState() 
    {
        return this.state;
    }
}

public class Client 
{
    public static void main(String[] args) 
    {
        ConcreteSubject subject = new ConcreteSubject();
        Observer observer1 = new ConcreteObserver();
        Observer observer2 = new ConcreteObserver();
 
        //aggiungo 2 observer che saranno notificati
        subject.addObserver(observer1);
        subject.addObserver(observer2);
 
        //modifico lo stato
        subject.setState( true );
 
        //rimuovo il primo observer che non sarà + notificato
        subject.deleteObserver(observer1);
 
        //modifico lo stato
        subject.setState( false );
 
    }
}

Nel metodo setState() occorre invocare il metodo setChanged() che esprime la volontà di notificare un cambiamento agli osservatori. Infatti se commentiamo questo metodo, gli osservatori non saranno avvisati nonostante l’invocazione del metodo notifyObservers(). Ciò avviene in quanto nel metodo notifyObservers(), all'interno della classe  java.util.Observable, è presente una semplice condizione “if (!changed)” che forza l’uscita dal metodo qualora il cambio non venga confermato. Qui di seguito vediamo il codice effettivo del metodo notifyObservers() della classe java.util.Observable:

public void notifyObservers(Object arg)
{
    Object[] arrLocal;
    synchronized (this)
    {
        if (!changed)
            return;
        arrLocal = obs.toArray();
        clearChanged();
    }
    for (int i = arrLocal.length-1; i>=0; i--)
        ((Observer)arrLocal[i]).update(this, arg);
}

L'interfaccia observable fornisce i metodi per registrare/depennare gli "Osservatori":

addObserver(Observer o)
removeObserver(Observer o)

Inoltre fornisce i metodi per notificare eventi agli Observer quando qualcosa nell’oggetto Observable cambia:

notifyObservers(Object arg)

Per notificare un cambiamento di stato basta chiamare notifyObservers() e attivare il flag changed utilizzando una chiamata al metodo setChanged().

Observer è un'interfaccia implementata dalle classi concrete che devono ricevere notifiche di eventi da parte delle classi Observable. Ha un unico metodo:

update(Observable o, Object arg)

che viene invocato quando si verifica un cambiamento nell’oggetto osservato.

Esempio: Semplice programma che utilizza il pattern Observer

Per compilare scriviamo:
C:\Java>"C:\Program Files\Java\jdk1.7.0_21\bin\javac.exe" EsempioPatternObserver.java
Per eseguire scriviamo:
C:\Java>"C:\Program Files\Java\jdk1.7.0_21\bin\java.exe" EsempioPatternObserver
Ho ricevuto un aggiornamento sul saldo: 30
import java.io.*;
import java.util.Observable;
import java.util.Observer;

public class EsempioPatternObserver
{
    public static void main(String args[]) 
    {
        Conto conto = new Conto(); // Osservabile
        ContoObserver ControllaConto = new ContoObserver();
        conto.addObserver(ControllaConto);
        conto.aggiornaSaldo(30);
    }
}

class ContoObserver implements Observer 
{
    public void update(Observable conto, Object saldo) 
    {
        System.out.println("Ho ricevuto un aggiornamento sul saldo: " + saldo);
    }
}

class Conto extends Observable 
{
    private Integer saldo = 0;
    public void aggiornaSaldo(Integer importo) 
    {
        saldo = importo;
        setChanged();
        notifyObservers(saldo);
    }
}

Il pattern Observer è utilizzato nel Model View Controller per notificare alla View eventi generati nel Model e nel Controller.

Model View Controller Pattern (MVC)

Un Pattern Architetturale definisce:
• il più alto livello di astrazione di un sistema software
• la struttura del sistema software in termini di
    – sottosistemi e relative responsabilità
    – linee guida per gestire le relazioni e l’interazione tra sottosistemi
La scelta del pattern architetturale è una scelta fondamentale ed influenza direttamente le fasi di progettazione e realizzazione del sistema software.

La struttura di una applicazione software, e più in generale di un sistema informativo, è caratterizzata da tre livelli

Presentazione: insieme delle componenti che gestiscono l'interazione con l'utente

Logica Applicativa: insieme delle componenti che realizzano le funzionalita richieste e gestiscono il flusso dei dati
Dati e Risorse: insieme delle componenti che gestiscono i dati che rappresentano le informazioni utilizzate dall'applicazione secondo il modello concettuale del dominio

MVC (acronimo di model-view controller) è un pattern architetturale (schema generale di riferimento) per la progettazione e strutturazione di applicazioni di tipo interattivo. Consente di separare e disaccoppiare il modello dei dati (model) e la logica applicativa (controller) dalle modalità di visualizzazione e interazione con l’utente (view).

Il paradigma MVC identifica, all’interno di un sistema informatico, tre tipologie di componenti:

- Model: rappresenta il modello dei dati di interesse per l’applicazione

- View: fornisce una rappresentazione grafica ed interattiva del model.

La logica di presentazione dei dati viene gestita solamente dalla View. Ciò implica che questa deve gestire la costruzione dell' interfaccia grafica (GUI) che rappresenta il mezzo mediante il quale gli utenti interagiranno con il sistema. Ogni GUI può essere costituita da schermate diverse che presentano più modi di interagire con i dati dell'applicazione. Per far sì che i dati presentati siano sempre aggiornati è possibile adottare due strategie note come "push model" e "pull model". Il "push model" adotta il pattern Observer, registrando le View come osservatori del Model. Le View possono quindi richiedere gli aggiornamenti al Model in base alle notifiche che il modello eroga. Con la strategia "pull model" la View richiede gli aggiornamenti quando "lo ritiene opportuno". Infine la View non decide la schermata da utilizzare ma delega ciò alla decisione del Controller (che grazie al pattern Strategy può anche essere anche cambiata dinamicamente al runtime). In definitiva la componente View si occupa della costruzione e della presentazione all'utente della schermata stessa.

- Controller: definisce la logica di controllo e le funzionalità applicative

Questo componente ha la responsabilità di trasformare le interazioni dell'utente della View in azioni eseguite dal Model. Il Controller non rappresenta un semplice "ponte" tra View e Model ma realizzando la mappatura tra input dell'utente e i processi eseguiti dal Model e selezionando la schermate della View richieste, il Controller implementa la logica di controllo dell'applicazione.

Il paradigma detta la distinzione di responsabilità tra le tre categorie e il flusso di informazioni tra di esse. In particolare:

• Model:
    notifica cambiamenti di stato/dei dati al view
View
    – fa riferimento al model e può interrogarlo per ottenere lo stato corrente
    – notifica al controller gli eventi generati dall’interazione con l’utente ed eventuali richieste di modifica
Controller
    – Riceve le richieste dal view (se sono di modifica aggiorna lo stato del model, se sono di selezione view richiama la view corrispondente)

Nella fase di inizializzazione dell’applicazione abbiamo i seguenti step:
1. viene creato il model
2. viene creato il view fornendo un riferimento al model
3. viene creato il controller fornendo riferimenti al model e al view
4. il view si registra come observer (listener) del model per ricevere  le notifiche di aggiornamento dal model (observable)
5. il controller si registra come observer (listener) della view per ricevere dal view (observable) gli eventi generati dall’utente. In realtà la connessione avviene mediante il meccanismo di propagazione e gestione eventi Swing/AWT quindi EventListener (es. ActionListener, MouseListener...) associati ai componenti grafici di view (es. JButton).

Quando l'utente interagisce con l’applicazione
1. il view riconosce l’azione dell’utente (es. pressione di un bottone) e la notifica al controller registrato come observer
2. il controller interagisce con il model per realizzare la funzionalità richiesta ed aggiornare/modificare lo stato o i dati
3. il model notifica al view registrato come observer le modifiche e gli aggiornamenti
4. il view aggiorna la visualizzazione sulla base del nuovo stato. Il nuovo stato e le info aggiornate per modificare la visualizzazione possono essere ottenuti dal view con

Il pattern Model-View-Controller risulta utile  per impostare la struttura generale dell’applicazione in fase di progettazione ma non definisce in maniera univoca schemi realizzativi ed implementazione.

Una progettazione architetturale che si basa sulla separazione dei ruoli dei componenti software e rende strutturalmente indipendenti moduli con funzionalità differenti favorisce qualità esterne ed interne del software

Qualità esterne:
>> estendibilità
    – semplicità di progetto e decentralizzazione dell’architettura
    – software facilmente estendibile agendo su moduli specifici
>> riusabilità
    – possibilità di estrarre e riutilizzare componenti
>> interoperabilità
    – interazione tra moduli con ruoli differenti
    – possibilità di creare gerarchie tra componenti

Qualità interne
>> strutturazione
    – struttura del software riflette le caratteristiche del dominio applicativo (dati + controllo + interazione e visualizzazione)
>> modularità
    – organizzazione del software in componenti con funzionalità definite
>> comprensibilità
    – ruoli e funzioni dei componenti sono facilmente identificabili
>> manutenibilità
    – possibilità di intervenire su componenti specifici con effetti nulli o limitati su altri componenti

Nel paradigma MVC l’interazione tra componenti è basata su meccanismi di propagazione e gestione di eventi:

In Java si ha che l’interazione tra view e controller avviene in base al meccanismo di propagazione e gestione eventi Swing/AWT quindi EventListener (es. ActionListener, MouseListener...) associati ai componenti grafici di view (es. JButton)

L’interazione tra model e view avviene secondo il pattern Observer-Observable
• i componenti model estendono la classe Observable
• i componenti view implementano l’interfaccia Observer e si registrano presso i componenti model (model.addObserver(view))
• i componenti model notificano i cambiamenti ai componenti view registrati come observer (notifyObservers())
• i componenti view ricevono le notifiche (update(model)) e aggiornano la visualizzazione

In pratica, View e Model sono relazionati tramite il pattern Observer dove la View "osserva" il Model. Anche il legame tra View e Controller è caratterizzato da un pattern Observer (implementato in java mediante la gestione degli eventi Swing/AWT e non mediante la classe Observable e l'interfaccia Observer) ma in questo caso è la View ad essere "osservata" dal Controller.

Nel pattern MVC tra i 3 soggetti esiste un forte legame in merito al cambiamento di stato. In particolare il Controller è interessato ai cambiamenti di stato della View, mentre la View è interessata ai cambiamenti di stato del Model. Il Model è osservato dalla View mentre il Controller è un osservatore della View. Quindi il pattern Observer trova applicazione 2 volte nell’MVC su coppie di soggetti diversi (Model-View e View-Controller). La View svolge un ruolo doppio poichè si trova ad essere osservata dal Controller e nello stesso tempo ad essere osservatore nei confronti del Model a differenza del Model e del Controller che invece giocano un ruolo singolo .

Esempio 1: Programma Termometro (MVC)

Vediamo un'applicazione progettata seguendo il pattern architetturale MVC

Listato del programma

/*
 * NOTA: questo e' un semplice esempio didattico.
 * Spesso la separazione tra model-view-controller non e' netta e definita.
 * In questo esempio ho considerato come unico observer la view intera ma
 * potevo considerare i 3 elementi:
 * SLIDER => controllo (e' sia di input che di output)
 * ANALOGVIEW => e' il termometro grafico (è sia di input che di output)
 * DIGITALVIEW => mostra la temperatura. E' solo di output
 * Cambiando il valore della temperatura nel MODEL l'observer VIEW aggiorna i 
 * controlli indicati nel metodo UPDATE
 */
public class Applicazione 
{
    public static void main(String[] args) 
    {
        TemperaturaModel model = new TemperaturaModel();  // Creazione Modello
        TemperaturaView mainFrame = new TemperaturaView();// Creazione View
        model.addObserver(mainFrame);                     // Pongo la View come Observer
        // Potevo porre come Observer i 3 elementi della maschera: 
        // ANALOGVIEW (In/Out) - SLIDER (In/Out) - DIGITALVIEW (Out)
        TemperaturaController controller=new TemperaturaController(model, mainFrame);
        mainFrame.setVisible(true);
    }
}

Listato del MODEL

import java.util.Observable;

public class TemperaturaModel extends Observable 
{
    private int temperatura;

    public TemperaturaModel() 
    {
        temperatura = 0;
    }
    // Metodo di lettura per le VIEW
    public int getValoreTemperatura() 
    {
        return temperatura;
    }
    // Metodo di scrittura per i Controller
    public void setValoreTemperatura(int valore) 
    {
        if (valore >= 0 && valore <= 100) 
        {
            temperatura = valore;
            setChanged();
            notifyObservers(temperatura);
        }
    }
}

Listato del VIEW

import java.util.Observer;
import static javax.swing.JFrame.*;

public class TemperaturaView extends JFrame implements Observer 
{
    public JSlider slider;
    private final JLabel tempLabel;
    public AnalogView analogico;
  
    public TemperaturaView() 
    {
        super("Temperatura");

        // --- 1° Componente ------------------------------
        // Etichetta che mostra la temperatura
        // ------------------------------------------------
        tempLabel = new DigitalView();

        // --- 2° Componente ------------------------------
        // Etichetta che mostra la temperatura
        // ------------------------------------------------
        slider = new JSlider(0, 100, 0);
        slider.setMajorTickSpacing(10);
        slider.setMinorTickSpacing(2);
        slider.setPaintTicks(true);
        slider.setPaintLabels(true);
        slider.setBorder(BorderFactory.createEmptyBorder(0, 15, 15, 15));

        // --- 3° Componente ------------------------------
        // Termometro grafico
        // ------------------------------------------------
        analogico = new AnalogView();

        // Definizione Layout dei controlli sul JFrame
        JPanel pannelloCentrale = new JPanel();
        pannelloCentrale.setLayout(new BoxLayout(pannelloCentrale, BoxLayout.X_AXIS));
        pannelloCentrale.add(tempLabel);
        pannelloCentrale.add(Box.createRigidArea(new Dimension(50,0)));
        pannelloCentrale.add(analogico);
        getContentPane().add(pannelloCentrale, BorderLayout.CENTER);
        getContentPane().add(slider, BorderLayout.PAGE_END);

        setSize(600, 345);
        setResizable(false);
        setLocationRelativeTo(null); // Centro la finestra nello schermo
        setDefaultCloseOperation(EXIT_ON_CLOSE);
    }
    // Controllo analogico
    class DigitalView extends JLabel
    {
        DigitalView()
        {
            super("0° C");
            setFont(new Font("Arial", Font.BOLD, 150));
            setPreferredSize(new Dimension(470,150));
            setMaximumSize(getPreferredSize());
            setMinimumSize(getPreferredSize());
            setHorizontalAlignment(JLabel.RIGHT);
        }
    }
    @Override
    public void update(Observable model, Object valore) 
    {
        tempLabel.setText((Integer)valore + "° C");
        slider.setValue((Integer)valore);
        analogico.setTemperature((Integer)valore);
    }
}

Listato del Controllo ANALOGVIEW (è parte del VIEW)

import java.awt.*;  // Color, Dimension, Graphics ...
import javax.swing.JPanel;

public class AnalogView extends JPanel 
{
    private static final int top = 1;    // Posizione del controllo nella finestra
    private static final int left = 11;  // principale
    private static final int width = 20;
    private static final int height = 200;
    private static final int MAX_TEMP = 100;
    private static final int MIN_TEMP = 0;

    private int valoreCorrente = 0;
    private Color colore = Color.GREEN;
  
    public AnalogView() 
    {
        setMaximumSize(new Dimension(43,240));
    }
  
    public void paintComponent(Graphics g) 
    {
        super.paintComponent(g);
        // Rettangolo con sotto ...
        g.setColor(Color.black); 
        g.drawRect(left, top, width, height);
        // ... un cerchio pieno di "colore"
        g.setColor(colore);  // Riempimento dell'ovale
        g.fillOval(left-width/2, top+height-3,width*2, width*2);
        g.setColor(Color.black); // Bordo dell'ovale
        g.drawOval(left-width/2, top+height-3,width*2, width*2);
        g.setColor(Color.white); // Riempimento rettangolo
        g.fillRect(left+1,top+1, width-1, height-1);
        g.setColor(colore); // Riempimento livello del termometro
        long redtop = height*(valoreCorrente-MAX_TEMP)/(MIN_TEMP-MAX_TEMP);
        g.fillRect(left+1, top + (int)redtop+1, width-1, height-(int)redtop);
    }
  
    public void setTemperature(int t)
    {
        valoreCorrente = t;
        if (valoreCorrente <= 30)
          colore = Color.GREEN;
        else if (valoreCorrente <= 70)
          colore = Color.ORANGE;
        else
          colore = Color.RED;
        repaint();
    }
}

Listato del CONTROLLER

public class TemperaturaController 
{
    private final TemperaturaModel M;
    private final TemperaturaView V;
    TemperaturaController(TemperaturaModel model, TemperaturaView view) 
    {
        M = model;
        V = view;
        // controller si registra come listener presso il view
        V.slider.addChangeListener(new SliderController(M));
        V.analogico.addMouseMotionListener(new AnalogMoveController(M));
        V.analogico.addMouseListener(new AnalogController(M));
    }
    // Controller Drag del mouse sul termometro
    class AnalogMoveController implements MouseMotionListener
    {
        private final TemperaturaModel termoModel;
        AnalogMoveController(TemperaturaModel model) 
        {
            termoModel = model;
        }
        @Override
        public void mouseDragged(MouseEvent me)
        {
            // Notifica al model del cambiamento di stato
            termoModel.setValoreTemperatura((201-me.getY())/2);
        }
        @Override
        public void mouseMoved(MouseEvent me) {}
    }
    // Controller Click del mouse sul termometro
    class AnalogController implements MouseListener
    {
        private final TemperaturaModel termoModel;
        AnalogController(TemperaturaModel model) 
        {
            termoModel = model;
        }
        @Override
        public void mouseClicked(MouseEvent me) {}
        @Override
        public void mousePressed(MouseEvent me) 
        {
            // Notifica al model del cambiamento di stato
            termoModel.setValoreTemperatura((201-me.getY())/2);
        }
        @Override
        public void mouseReleased(MouseEvent me) {}
        @Override
        public void mouseEntered(MouseEvent me) {}
        @Override
        public void mouseExited(MouseEvent me) {}
    }
    // Controller dello Slider
    class SliderController implements ChangeListener 
    {
        private final TemperaturaModel termoModel;
        SliderController(TemperaturaModel model) 
        {
            termoModel = model;
        }
        @Override
        public void stateChanged(ChangeEvent e) 
        {
            // Notifica al model del cambiamento di stato
            termoModel.setValoreTemperatura(((JSlider)e.getSource()).getValue());
        }
    }
}

Lo schema MVC dell'applicazione può essere riassunto con questo schema

Il progetto completo è scaricabile qui.

Esempio 2: Programma Chat locale (MVC) senza librerie

In questo esempio non vengono utilizzate la classe Observable ne l'interfaccia Observer del Java

Listato del programma (ChatApp.java)

public class ChatApp 
{
    public static void main(String[] args) 
    {
        ChatModel model = new ChatModel();
        ChatView view1 = new ChatView(model);
        ChatView view2 = new ChatView(model);
        ChatView view3 = new ChatView(model);
        ChatController controller1 = new ChatController(view1, model);
        ChatController controller2 = new ChatController(view2, model);
        ChatController controller3 = new ChatController(view3, model);
    }
}

Listato dell'interfaccia chatObservable

public interface ChatObservable 
{
    void addObserver(ChatObserver forumObserver);
    void notifyObservers(String Command, String arg);
}

Listato dell'interfaccia chatObserver

public interface ChatObserver 
{
    void update(String Cmd, String Arg);
}

Listato della componente chatController

// Osserva il VIEW 
public class ChatController implements ChatObserver
{
    private final ChatModel model;
    private final ChatView view;

    public ChatController(ChatView v, ChatModel m) 
    {
        view = v;
        model = m;
        view.addObserver(this); // Si pone come observer di VIEW
    }
    // Implementazione metodo UPDATE dell interfaccia ChatObserver
    // Esegue i comandi ricevuti dall'interfaccia (VIEW)
    @Override
    public void update(String Command, String arg) 
    {
        switch (Command) 
        {
            case "doCmd.SendMessage":
                model.addMessage(arg);
                break;
            case "doCmd.ChangeView":
                view.showDisplay(Integer.parseInt(arg));
                break;
        }
    }
}

Listato della classe chatView

import java.util.*; // ArrayList, Collections, Enumeration
import javax.swing.*;
import java.awt.*; // Container
import java.awt.event.*;
// Osserva il MODEL, Osservato dal CONTROLLER
public final class ChatView implements ChatObserver, ChatObservable
{
    private final ArrayList chatObserversVector = new ArrayList();
    private final ChatModel model;
    private final JScrollPane scroll;
    private final Container container;
    private final JButton submit;
    private final ButtonGroup group;
    private final JRadioButton firstGui;
    private final JRadioButton secondGui;
    private final JTextArea textArea;
    private final JPanel panel;
    private final JTextField textField;
    private final JFrame frame;
    private static int counter = 0; // conta le finestre
    private int id;

    public ChatView(ChatModel model) 
    {
        this.model = model;
        id = ++counter;
        frame = new JFrame("Client " + id);
        scroll = new JScrollPane();
        container = frame.getContentPane();
        textArea = new JTextArea("", 10, 40);
        textArea.setEditable(false);
        panel = new JPanel();
        textField = new JTextField(20);
        submit = new JButton("Submit");
        group = new ButtonGroup();
        firstGui = new JRadioButton("FirstGui", true);
        secondGui = new JRadioButton("SecondGui");
        container.setLayout(new BorderLayout());
        secondGui.addItemListener(selectionHandler);
        submit.addActionListener(submitHandler);
        model.addObserver(this);
        CreateView(BorderLayout.SOUTH);
        frame.setLocation(0,(id-1)*frame.getHeight());
    }
    // Creazione della Finestra
    public void CreateView(String BorderPosition) 
    {
        scroll.setViewportView(textArea);
        container.add(scroll, BorderLayout.CENTER);
        container.add(panel, BorderPosition);
        group.add(firstGui);
        group.add(secondGui);
        panel.add(firstGui);
        panel.add(secondGui);
        panel.add(textField);
        panel.add(submit);
        frame.pack();
        frame.setVisible(true);
    }
    // Cambio di Vista
    public void showDisplay(int display) 
    {
        String text = model.getMessages();
        frame.setVisible(false);
        textArea.setText(text);
        if (display == ItemEvent.SELECTED) 
            CreateView(BorderLayout.NORTH);
        else 
            CreateView(BorderLayout.SOUTH);
    }
    // Listener
    ItemListener   selectionHandler = new ItemListener() 
    {
        @Override
        public void itemStateChanged(ItemEvent e) 
        {   // Notifica al Controller: Cambio Vista
            notifyObservers("doCmd.ChangeView", Integer.toString(e.getStateChange()));
        }
    };
    ActionListener submitHandler = new ActionListener() 
    {
        @Override
        public void actionPerformed(ActionEvent e) 
        {   // Notifica al Controller: Invio Messaggio
            if (textField.getText().trim().length()>0)
                notifyObservers("doCmd.SendMessage", "Client"+id+": "+textField.getText());
        }
    };
    // Implementazione interfaccia Observer e Observable 
    @Override
    public void update(String Cmd, String Arg)
    {
        textArea.setText(model.getMessages());
        textField.setText("");
    }
    @Override
    public void addObserver(ChatObserver chatObserver) 
    {
        chatObserversVector.add(chatObserver);
    }
    @Override
    public void notifyObservers(String Cmd, String Arg) 
    {
        Enumeration e =  Collections.enumeration(chatObserversVector);
        while (e.hasMoreElements()) 
            ((ChatObserver)e.nextElement()).update(Cmd,Arg);
    }
}

Listato della classe chatModel

import java.util.*;
// Osservato da VIEW 
public class ChatModel implements ChatObservable 
{
    private final ArrayList messages;
    private final ArrayList forumObserversVector = new ArrayList();

    public ChatModel() 
    {
        messages = new ArrayList();
    }
    public void addMessage(String msg) 
    {
        messages.add(msg);
        notifyObservers("doCmd.UpdateView",this.getMessages());
    }
    public String getMessages() 
    {
        String allMessages = "";
        for (int i = 0; i < messages.size(); ++i)
        {
            if (!allMessages.equals("")) 
                allMessages += "\n";
            allMessages += (String)messages.get(i);
        }
        return allMessages;
    }
    // Implementazione interfaccia Observable 
    @Override
    public void addObserver(ChatObserver forumObserver)
    {
        forumObserversVector.add(forumObserver);
    }
    @Override
    public void notifyObservers(String Cmd, String Arg)
    {
        Enumeration e = Collections.enumeration(forumObserversVector);
        while (e.hasMoreElements())
           ((ChatObserver)e.nextElement()).update(Cmd,Arg);
    }
}

Lo schema MVC dell'applicazione può essere riassunto con questo schema

Il progetto completo è scaricabile qui.

Esempio 3: Programma Cambio Valuta (MVC)

In questo esempio ...

Listato del programma



Esempio 4: Programma Schiaccia la talpa (MVC)

Vediamo un'altra applicazione progettata seguendo il pattern architetturale MVC:

Listato del programma

/*
 * NOTA: questo e' un semplice esempio didattico.
 * Spesso la separazione tra Model-View-Controller non e' netta e definita.
 * In questo esempio ho considerato come unico observer la VIEW ma
 * potevo includere anche i 3 elementi:
 * TALPA => La talpa che appare e scompare
 * PUNTEGGIO => il punteggio
 * TERRENO => mostra lo sfondo.
 * Colpendo la talpa, il CONTROLLER genera un evento che nel MODEL
 * determina un cambio di stato (M.TalpaColpita). L'observer VIEW 
 * tramite il metodo UPDATE aggiorna la videata in modo opportuno.
 */
public class SchiacciaLaTalpa 
{
    public static void main(String[] args) throws InterruptedException 
    {
        TalpaModel model=new TalpaModel(); // Creazione Modello
        TalpaView view=new TalpaView();    // Creazione View
        model.addObserver(view);           // Pongo la View come Observer
        TalpaController Ctrl =new TalpaController(view,model);
        view.MostraTalpaCasualmente(); // Preparo la visualizzazione della talpa
    }
}

Listato del MODEL

import java.util.Observable;
/* ---------------------------------------------------------------------------
    Il Model: 
    - definisce lo stato/dati correnti dell’applicazione
    - gestisce l’accesso allo stato /dati
        > per la VIEW, offre metodi che espongano i dati in sola lettura 
        > per il CONTROLLER mette a disposizione i metodi che consentono  
          di modificare lo stato in base alle richieste dell’utente;
    - notifica alla VIEW i cambiamenti di stato/dati affinché questi possano 
      essere evidenziati a video.
-- ------------------------------------------------------------------------ */
public class TalpaModel extends Observable
{
    private int Punti;
    private boolean TalpaAttivata;
    
    public TalpaModel()
    {
        Punti=0;
        TalpaAttivata=false;
        setChanged();
        notifyObservers("INIT");
    }

    public int getPunti()
    {
        return Punti;
    }

    public void DisattivaTalpa()
    {
        Punti=0;
        TalpaAttivata=false;
        setChanged();
        notifyObservers("OFF");
    }
    
    public void AttivaTalpa()
    {
        Punti=0;
        TalpaAttivata=true;
        setChanged();
        notifyObservers("ON");
    }

    public void TalpaColpita()
    {
        Punti++;
        setChanged();
        notifyObservers("HIT");
    }
    
    public void TalpaSvanita()
    {
        setChanged();
        notifyObservers("VANISH");
    }

    public void TalpaMancata()
    {
        if (TalpaAttivata==true)
        {
            Punti--;
            setChanged();
            notifyObservers("MISS");
        }
    }
}

Listato del VIEW

import java.util.Observer;
import java.util.Observable;
import java.awt.*;  // Point, Toolkit, ActionListener, GridLayout, Font ...
import javax.swing.*; // JFrame, JComboBox ...
import javax.swing.border.*;
/* -----------------------------------------------------------------------
VIEW: fornisce la rappresentazione grafica ed interattiva del MODEL. 
- definisce le modalità di presentazione dei dati e dello stato dell’applicazione
- consente l’interazione con l’utente
- riceve notifiche dal MODEL e aggiorna la visualizzazione 
- demanda al CONTROLLER l'esecuzione dei processi richiesti dall'utente e 
  la scelta delle eventuali schermate da presentare.
-------------------------------------------------------------------------- */
public class TalpaView extends JFrameCentered implements Observer  
{
    // final per indicare che viene inizializzata una sola volta
    final public JButton Reset;
    final public JButton Start;
    final private Punteggio Punti; 
    final private Talpa T;
    final private Terreno Sfondo;
    final private Titolo Tit;
 
    // *************************************************
    // TITOLO DELLA MASCHERA
    // *************************************************
    public TalpaView()
    {
        // La keyword super usata nel costruttore serve per riferirsi alla
        // classe padre i.e. quella da cui si deriva. In casi come questo 
        // nei quali la classe padre ha un costruttore con parametri 
        // la classe figlia (derivata) ha l’obbligo di chiamare esplicitamente
        // il costruttore del padre che invece è implicitamente chiamato 
        // nel caso che sia presente un costruttore vuoto (senza argomenti).
        super(945,582,"Schiaccia la Talpa");
        // ---------------------------------------------------
        // Creo le varie Componenti ...
        Tit = new Titolo("Schiaccia la Talpa".toUpperCase());
        T=new Talpa(); // Talpa
        Sfondo=new Terreno(); // Sfondo
        // ---------------------------------------------------
        // ... e le dispongo nel pannello dell'applicazione
        JPanel PannelloCentrale = new JPanel(null); 
        PannelloCentrale.add(T);
        PannelloCentrale.add(Sfondo);
        PannelloCentrale.setComponentZOrder(Sfondo, 1);
        PannelloCentrale.setComponentZOrder(T, 0); // davanti a tutto
        // ---------------------------------------------------
        // Parte pie pagina
        JPanel piepagina = new JPanel();
        // ------- Gestione del Layout: LINE_AXIS -------------------------
        // |   PUNTI   |   HorizontalGlue   | START   | RigidArea | RESET |
        // ----------------------------------------------------------------
        piepagina.setLayout(new BoxLayout(piepagina, BoxLayout.LINE_AXIS));
        // Bordo doppio
        Border BordoEst=BorderFactory.createMatteBorder(0,1,1,1,Color.black);
        Border BordoInt=BorderFactory.createEmptyBorder(5, 5, 5, 5);
        Border BordoComposto=BorderFactory.createCompoundBorder(BordoEst, BordoInt);
        piepagina.setBorder(BordoComposto);
        // Aggiungo gli elementi sul Pie pagina
        piepagina.add(new JLabel("Punteggio: "));
        piepagina.add(Punti=new Punteggio());
        piepagina.add(Box.createHorizontalGlue());
        piepagina.add(Start = new JButton("START"));
        piepagina.add(Box.createRigidArea(new Dimension(10, 0)));
        piepagina.add(Reset = new JButton("RESET"));
        // ***********************************************
        // ------------- Gestione del Layout
        // |   NORTH   |
        // -------------
        // |   CENTER  |
        // -------------
        // |   SOUTH   |
        // -------------
        // ***********************************************
        getRootPane().setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
        getContentPane().add(Tit,BorderLayout.NORTH);
        getContentPane().add(PannelloCentrale,BorderLayout.CENTER);
        getContentPane().add(piepagina,BorderLayout.SOUTH);
        setVisible(true); // Mostro la finestra del gioco
    }

    public void MostraTalpaCasualmente() throws InterruptedException
    {
        for (;;) 
            T.CompariCasualmente();
    }
    
    public Talpa getTalpa()
    {
        return T;
    }
    
    public Terreno getSfondo()
    {
        return Sfondo;
    }
    // ------------------------------------------------------
    // Classe interna relativa al titolo
    class Titolo extends JLabel
    {
        public Titolo(String frase)
        {
            Font font = new Font("Serif", Font.ITALIC, 20);
            setText(frase);
            this.setHorizontalAlignment( SwingConstants.CENTER);
            setFont(font);
            setForeground(Color.blue);
            setOpaque(true);
            setBackground(new Color(0,0,0,64));
            setBorder(BorderFactory.createLineBorder(Color.black));
        }
    }
    @Override
    public void update(Observable model, Object valore) 
    {
        int p=((TalpaModel) model).getPunti();
        Punti.SetPunteggio(p);
        if ((valore=="OFF") || (valore=="INIT")) // Sospendo la visualizzazione  della talpa
            T.Disattivata();
        else if (valore=="ON") // Inizio a mostrare la talpa
            T.Attivata();
        else if (valore=="MISS") // Talpa mancata
            T.Hide();
        else if (valore=="HIT") // Talpa colpita (pressione mouse)
            T.Hit();
        else if (valore=="VANISH") // Talpa colpita (rilascio del mouse)
            T.Vanish();
    }
}

Listato del CONTROLLER

import java.awt.event.*;
/* -- ------------------------------------------------------------------
CONTROLLER: definisce la logica di controllo e le funzionalità applicative
- gestisce gli eventi conseguenti ai comandi dell’utente
- opera sul MODEL apportando modifiche, aggiornamenti, inserimenti in base
  agli eventi ed ai comandi ricevuti
----------------------------------------------------------------------- */
public class TalpaController  
{
    private final TalpaModel M;
    private final TalpaView V;
    public TalpaController(TalpaView view, TalpaModel model)
    {
        M=model;
        V=view;
        // --------------------------------------------
        // Imposto i vari Listener
        V.Reset.addActionListener(new ResetController());
        V.Start.addActionListener(new StartController());
        V.getSfondo().addMouseListener(new ColpoMancatoController());
        // Esempio di definizione del listener mediante 
        // un'anonymous inner class listeners 
        MouseAdapter ColpoRiuscitoController = new MouseAdapter() 
        {
            @Override
            public void mousePressed(MouseEvent e) 
            {
                M.TalpaColpita();
            }
            @Override
            public void mouseReleased(MouseEvent e) 
            {
                M.TalpaSvanita();
            }
            @Override
            public void mouseExited(MouseEvent e) 
            {
                M.TalpaSvanita();
            }
        };
        V.getTalpa().addMouseListener(ColpoRiuscitoController);
   }
    
   class ColpoMancatoController extends MouseAdapter
   {
        @Override
        public void mousePressed(MouseEvent e) 
        {
            M.TalpaMancata();
        }
   }
   class ResetController implements ActionListener
   {
       @Override
       public void actionPerformed(ActionEvent event) 
       {
            M.DisattivaTalpa();
       }
   }
   class StartController implements ActionListener
   {
       @Override
       public void actionPerformed(ActionEvent event) 
       {
            M.AttivaTalpa();
       }
   }
}

Listato del Controllo TALPA (utilizzato nella VIEW)

import javax.swing.*; // Necessaria per JLabel
import java.awt.*; // Cursor, Image, Point, Toolkit
import java.util.Random;

public  class Talpa extends JLabel
{
    // Le costanti vengono definite come: "static final"
    private static final int MaxDurataV=2000;
    private static final int MaxDurataN=1000;
    private static final int MinDurata=500;
    private static final int[][] Posiz = 
                        { 
                            {98, 3}, {304 ,3} , {510 ,3} , {716 ,3},
                            {98, 131}, {304 ,131} , {510 ,131} , {716 ,131},
                            {98, 270}, {304 ,270} , {510 ,270} , {716 ,270}
                        };
    private final Random randomTalpa;
    final private Random randomDurataVisib;
    final private Random randomDurataNasc;
    private boolean InEsecuzione;
    private boolean Beccata;
    private final Cursor cursor;

    public Talpa()
    {
        Toolkit toolKit = Toolkit.getDefaultToolkit();
        Image boom = toolKit.getImage("src\\img\\boom.png");
        cursor = toolKit.createCustomCursor(boom, new Point(0, 0), "boom");
        setVisible(false);  // Inizialmente la talpa non è visibile
        InEsecuzione=false; // e neppure in esecuzione
        Beccata=false;
        setSize(115, 123);
        setIcon(new ImageIcon(toolKit.getImage("src\\img\\talpa.png")));
        setCursor(new Cursor(Cursor.HAND_CURSOR));
        // -----------------------------------------
        // Inizializzo i contatori casuali
        randomTalpa = new Random();
        randomDurataVisib = new Random();
        randomDurataNasc = new Random();
    }

    public void Attivata()
    {
        InEsecuzione=true;
    }
    public void Disattivata()
    {
        InEsecuzione=false;
        setVisible(false);
     }
    
    public void Show(int i)
    {
        // Sposto la talpa su una delle buche
        setLocation(Posiz[i][0], Posiz[i][1]);
        setVisible(true);
    }

    public void Hit()
    {
        Beccata=true; // Segno che ho beccato la talpa
        setCursor(cursor);
    }

    public void Vanish()
    {
        setCursor(new Cursor(Cursor.HAND_CURSOR));
        if (Beccata) // se ho beccato la talpa la nascondo
            setVisible(false);
        Beccata=false;
     }
        
    public void Hide()
    {
        setVisible(false);
    }
    
    @SuppressWarnings("empty-statement")
    public void CompariCasualmente() throws InterruptedException
    {
        if (!InEsecuzione) return;
        int Qualebuca=randomTalpa.nextInt(Posiz.length);
        int millsecN=randomDurataNasc.nextInt(MaxDurataN)+MinDurata;
        int millsecV=randomDurataVisib.nextInt(MaxDurataV)+MinDurata;
        Show(Qualebuca); //
        Thread.sleep(millsecV); // Lascio la talpa visibile per X millisecondi.
        while (Beccata); // Fino a quando premo il mouse sulla talpa aspetto!
        Hide(); 
        Thread.sleep(millsecN);
    }
}

Listato del Controllo TERRENO (utilizzato nella VIEW)

import java.awt.*;  // Color, Toolkit
import javax.swing.*; // JLabel, ImageIcon, BorderFactory

public class Terreno extends JLabel
{
    public Terreno()
    {
        
            Toolkit toolKit = Toolkit.getDefaultToolkit();
            setIcon(new ImageIcon(toolKit.getImage("src\\img\\sfondo.png")));
            /* 
             * Potevo caricare l'icona anche in questo modo. In questo caso
             * occorre effettuare l'import di: 
             *    import javax.imageio.ImageIO
             *    import java.io.*;
             *    try {
             *       setIcon(new ImageIcon(ImageIO.read(new File("img\\sfondo.png"))));
             *     } catch (IOException e) {}
            */
            setBorder(BorderFactory.createMatteBorder(0,1,1,1,Color.black));
            setLocation(0,0);
            setSize(929,477);
    }
}

Listato del Controllo PUNTEGGIO (utilizzato nella VIEW)

import javax.swing.*; // JFrame, JComboBox ...
import javax.swing.border.*;
import java.awt.*;  // Point, Toolkit, ActionListener, GridLayout, Font ...

public class Punteggio extends JLabel
{
        public Punteggio()
        {
            Font font = new Font("Courier New", Font.BOLD, 20);
            setText(Integer.toString(0));
            setFont(font);
            setPreferredSize(new Dimension(100,14));
            setHorizontalAlignment(JLabel.RIGHT);
            Border paddingBorder = BorderFactory.createEmptyBorder(10,10,10,10);
            Border border = BorderFactory.createMatteBorder(1,1,1,1,Color.red);
            setBorder(BorderFactory.createCompoundBorder(border, paddingBorder));
            setOpaque(true);
            setForeground(Color.YELLOW); 
            setBackground(Color.BLUE);  
        }

        void SetPunteggio(int p)
        {
            setText(Integer.toString(p));
        }
}

Listato della classe  JFrameCentered  (la classe VIEW è implementata come una sua estensione)

import javax.swing.*; // JFrame, JComboBox ...
import java.awt.*;    // Point, Toolkit, ActionListener, GridLayout, Font ...

public class JFrameCentered extends JFrame
{
    public JFrameCentered(int frameWidth, int frameHeight, String s)
    {
        super(); // Non necessario
        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
        Point center = GraphicsEnvironment.getLocalGraphicsEnvironment().getCenterPoint();
        setBounds((int) (screenSize.getWidth() - frameWidth)/2,center.y-frameHeight/2, frameWidth, frameHeight);
        setTitle(s);
        setResizable(false);
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
}

Lo schema MVC dell'applicazione può essere riassunto con questo schema

Il progetto completo è scaricabile qui.

Link utilizzati:

Blog Giuseppe dell'Abate (ITA) - https://dellabate.wordpress.com/category/gof-pattern

Learn Java Design Patterns (ENG) http://www.tutorialspoint.com/design_pattern/index.htm

Model View Controller Pattern -MVC (ITA) http://www.claudiodesio.com/ooa&d/mvc.htm

Design Patterns - Slide (ITA) http://home.deib.polimi.it/dubois/provafinale/designpatterns.pdf

Una versione multithreaded di ThObservable (ITA) http://www.mokabyte.it/1998/03/foresta.htm

Esercitazioni di Progettazione del Software - A.A. 2012/2013 - Docente: Massimo Mecella
Lezione 8 @ 10 maggio http://www.dis.uniroma1.it/~mecella/didattica/2013/EsercitazioniProgettazioneSoftware/registro.htm