Suche…


Einfaches MVP-Beispiel

Um ein einfaches Beispiel für die Verwendung des MVP-Musters zu veranschaulichen, betrachten Sie den folgenden Code, der eine einfache Benutzeroberfläche mit nur einer Schaltfläche und einem Label erstellt. Wenn Sie auf die Schaltfläche klicken, wird das Label mit der Häufigkeit aktualisiert, mit der die Schaltfläche angeklickt wurde.

Wir haben 5 Klassen:

  • Modell - Der POJO, der den Status beibehält (M in MVP)
  • Ansicht - Die Klasse mit UI-Code (V in MVP)
  • ViewListener - Schnittstelle, die Methoden zum Antworten auf Aktionen in der Ansicht bereitstellt
  • Presenter - Reagiert auf Eingaben und aktualisiert die Ansicht (P in MVP)
  • Anwendung - Die "Hauptklasse" zum Zusammenführen und Starten der App

Eine minimale „Modell“ Klasse , die nur eine einzige unterhält count variabel.

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

    public void addOneToCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

Eine minimale Benutzeroberfläche, um die Zuhörer zu benachrichtigen:

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

Die Ansichtsklasse erstellt alle Elemente der Benutzeroberfläche. Die Ansicht und nur die Ansicht sollten auf Elemente der Benutzeroberfläche verweisen (dh keine Schaltflächen, Textfelder usw. im Präsentator oder in anderen Klassen).

/**
 * 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);
    }
}

Die Benachrichtigungslogik kann auch in Java8 folgendermaßen codiert werden:

        ...
        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);
        // }
        
    });
}

Die Schnittstelle muss umgestaltet werden, um das ActionEvent als Parameter zu übernehmen:

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

Hier wird nur eine Benachrichtigungsmethode benötigt, die aktuelle Listener-Methode und ihre Parameter werden als Parameter übergeben. Falls erforderlich, kann dies auch für etwas weniger raffiniertes als die eigentliche Ereignisbehandlung verwendet werden. Dies funktioniert alles, solange es eine Methode in der Benutzeroberfläche gibt, z.

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

Der Moderator kann die Ansicht übernehmen und sich selbst als Zuhörer hinzufügen. Wenn Sie in der Ansicht auf die Schaltfläche klicken, werden alle Zuhörer (einschließlich des Präsentators) in der Ansicht benachrichtigt. Nachdem der Präsentator nun benachrichtigt wurde, kann er geeignete Maßnahmen ergreifen, um das Modell (dh den Status der Anwendung) zu aktualisieren, und anschließend die Ansicht entsprechend aktualisieren.

/**
 * 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()));
    }
}

Um alles zusammenzusetzen, kann die Ansicht erstellt und in den Moderator eingefügt werden. Ebenso kann ein Ausgangsmodell erstellt und eingefügt werden. Beide können zwar im Presenter erstellt werden, das Einfügen in den Konstruktor ermöglicht jedoch wesentlich einfachere Tests.

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
Lizenziert unter CC BY-SA 3.0
Nicht angeschlossen an Stack Overflow