Ricerca…


Esempio di MVP semplice

Per illustrare un semplice esempio di utilizzo del pattern MVP, prendere in considerazione il codice seguente che crea un'interfaccia utente semplice con solo un pulsante e un'etichetta. Quando si fa clic sul pulsante, l'etichetta si aggiorna con il numero di volte in cui si fa clic sul pulsante.

Abbiamo 5 classi:

  • Modello: il POJO per mantenere lo stato (M in MVP)
  • Visualizza - La classe con codice UI (V in MVP)
  • ViewListener - Interfaccia che fornisce metodi per rispondere alle azioni nella vista
  • Presentatore: risponde all'input e aggiorna la vista (P in MVP)
  • Applicazione: la classe "principale" per riunire tutto e avviare l'app

Una classe "modello" minima che mantiene solo una variabile count singolo.

/**
 * A minimal class to maintain some state 
 */
public class Model {
    private int count = 0;

    public void addOneToCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

Un'interfaccia minima per notificare gli ascoltatori:

/**
 * Provides methods to notify on user interaction
 */
public interface ViewListener {
    public void onButtonClicked();
}

La classe view costruisce tutti gli elementi dell'interfaccia utente. La vista e solo la vista dovrebbero avere riferimento agli elementi dell'interfaccia utente (ad esempio, nessun pulsante, campi di testo, ecc. Nel relatore o in altre classi).

/**
 * Provides the UI elements
 */

import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.WindowConstants;

public class View {
    // A list of listeners subscribed to this view
    private final ArrayList<ViewListener> listeners;
    private final JLabel label;
    
    public View() {
        final JFrame frame = new JFrame();
        frame.setSize(200, 100);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setLayout(new GridLayout());

        final JButton button = new JButton("Hello, world!");

        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(final ActionEvent e) {
                notifyListenersOnButtonClicked();
            }
        });
        frame.add(button);

        label = new JLabel();
        frame.add(label);

        this.listeners = new ArrayList<ViewListener>();

        frame.setVisible(true);
    }

    // Iterate through the list, notifying each listner individualy 
    private void notifyListenersOnButtonClicked() {
        for (final ViewListener listener : listeners) {
            listener.onButtonClicked();
        }
    }

    // Subscribe a listener
    public void addListener(final ViewListener listener) {
        listeners.add(listener);
    }

    public void setLabelText(final String text) {
        label.setText(text);
    }
}

La logica di notifica può anche essere codificata in questo modo in Java8:

        ...
        final Button button = new Button("Hello, world!");
        // In order to do so, our interface must be changed to accept the event parametre
        button.addActionListener((event) -> {
            notifyListeners(ViewListener::onButtonClicked, event);
            // Example of calling methodThatTakesALong, would be the same as callying:
            // notifyListeners((listener, long)->listener.methodThatTakesALong(long), 10L)
            notifyListeners(ViewListener::methodThatTakesALong, 10L);
        });
        frame.add(button);
        ...

/**
 * Iterates through the subscribed listeneres notifying each listener individually.
 * Note: the {@literal '<T>' in private <T> void} is a Bounded Type Parametre. 
 *
 * @param <T>      Any Reference Type (basically a class).
 * 
 * @param consumer A method with two parameters and no return, 
 *                 the 1st parametre is a ViewListner, 
 *                 the 2nd parametre is value of type T.
 * 
 * @param data     The value used as parametre for the second argument of the
 *                 method described by the parametre consumer.
 */
private <T> void notifyListeners(final BiConsumer<ViewListener, T> consumer, final T data) {
    // Iterate through the list, notifying each listener, java8 style 
    listeners.forEach((listener) -> {

        // Calls the funcion described by the object consumer.
        consumer.accept(listener, data);

        // When this method is called using ViewListener::onButtonClicked
        // the line: consumer.accept(listener,data); can be read as:
        // void accept(ViewListener listener, ActionEvent data) {
        //     listener.onButtonClicked(data);
        // }
        
    });
}

L'interfaccia deve essere sottoposta a refactoring per poter utilizzare ActionEvent come parametro:

public interface ViewListener {
    public void onButtonClicked(ActionEvent evt);
    // Example of methodThatTakesALong signature
    public void methodThatTakesALong(long );
}

Qui è necessario un solo metodo-notifica, il metodo listener effettivo e il suo parametro vengono passati come parametri. Nel caso in cui ciò sia necessario, questo può essere usato anche per qualcosa di un po 'meno ingegnoso dell'effettiva gestione degli eventi, funziona tutto il tempo che c'è un metodo nell'interfaccia, ad esempio:

        notifyListeners(ViewListener::methodThatTakesALong, -1L);

Il relatore può prendere visione e aggiungersi come ascoltatore. Quando si fa clic sul pulsante nella vista, la vista notifica tutti i listener (incluso il relatore). Ora che il relatore viene avvisato, può intraprendere l'azione appropriata per aggiornare il modello (ovvero lo stato dell'applicazione), quindi aggiornare la vista di conseguenza.

/**
 * Responsible to responding to user interaction and updating the view
 */
public class Presenter implements ViewListener {
    private final View view;
    private final Model model;

    public Presenter(final View view, final Model model) {
        this.view = view;
        view.addListener(this);
        this.model = model;
    }

    @Override
    public void onButtonClicked() {
        // Update the model (ie. the state of the application)
        model.addOneToCount();
        // Update the view
        view.setLabelText(String.valueOf(model.getCount()));
    }
}

Per mettere tutto insieme, la vista può essere creata e iniettata nel presentatore. Allo stesso modo, un modello iniziale può essere creato e iniettato. Mentre entrambi possono essere creati nel presenter, iniettarli nel costruttore consente di eseguire test molto più semplici.

public class Application {
    public Application() {
        final View view = new View();
        final Model model = new Model();
        new Presenter(view, model);
    }

    public static void main(String... args) {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                new Application();
            }
        });
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Autorizzato sotto CC BY-SA 3.0
Non affiliato con Stack Overflow