Zoeken…


Eenvoudig MVP-voorbeeld

Om een eenvoudig voorbeeld van het gebruik van het MVP-patroon te illustreren, overweeg de volgende code die een eenvoudige gebruikersinterface maakt met alleen een knop en een label. Wanneer op de knop wordt geklikt, wordt het label bijgewerkt met het aantal keren dat op de knop is geklikt.

We hebben 5 klassen:

  • Model - De POJO om de status te behouden (M in MVP)
  • Beeld - De klas met UI-code (V in MVP)
  • ViewListener - Interface die methoden biedt om te reageren op acties in de weergave
  • Presenter - Reageert op invoer en werkt de weergave bij (P in MVP)
  • Toepassing - De "hoofdklasse" om alles samen te brengen en de app te starten

Een minimale "model" -klasse die slechts een enkele count behoudt.

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

    public void addOneToCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

Een minimale interface om de luisteraars op de hoogte te stellen:

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

De viewklasse construeert alle UI-elementen. De weergave, en alleen de weergave, moet verwijzen naar UI-elementen (dwz geen knoppen, tekstvelden, enz. In de presentator of andere 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);
    }
}

De meldingslogica kan ook als volgt worden gecodeerd 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);
        // }
        
    });
}

De interface moet opnieuw worden aangepast om ActionEvent als parameter te kunnen gebruiken:

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

Hier is slechts één kennisgevingsmethode nodig, de daadwerkelijke luisteraarmethode en de parameter worden doorgegeven als parameters. In het geval dat dit nodig is, kan dit ook worden gebruikt voor iets minder handig dan de daadwerkelijke afhandeling van gebeurtenissen, het werkt allemaal zolang er een methode in de interface is, bijvoorbeeld:

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

De presentator kan het uitzicht overnemen en zichzelf toevoegen als luisteraar. Wanneer op de knop in de weergave wordt geklikt, worden alle luisteraars (inclusief de presentator) op de hoogte gebracht. Nu de presentator op de hoogte is gesteld, kan deze passende actie ondernemen om het model bij te werken (dwz de status van de toepassing) en vervolgens de weergave dienovereenkomstig bij te werken.

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

Om alles samen te voegen, kan de weergave worden gemaakt en in de presentator worden geïnjecteerd. Evenzo kan een eerste model worden gemaakt en geïnjecteerd. Hoewel beide in de presenter kunnen worden gemaakt, kunnen ze veel eenvoudiger worden geïnjecteerd in de constructor.

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow