Sök…


Enkelt MVP-exempel

För att illustrera ett enkelt exempel på användning av MVP-mönstret, överväg följande kod som skapar en enkel UI med bara en knapp och en etikett. När du klickar på knappen uppdateras etiketten med antalet gånger knappen har klickats.

Vi har 5 klasser:

  • Modell - POJO för att upprätthålla tillstånd (M i MVP)
  • Visa - Klassen med UI-kod (V i MVP)
  • ViewListener - Gränssnitt som tillhandahåller metoder för att svara på åtgärder i vyn
  • Presenter - svarar på inmatning och uppdaterar vyn (P i MVP)
  • Applikation - "Huvudklassen" för att dra samman allt och starta appen

En minimal "modell" klass som bara upprätthåller en enda count .

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

    public void addOneToCount() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

Ett minimalt gränssnitt för att meddela lyssnarna:

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

Visningsklassen konstruerar alla UI-element. Vyn och endast vyn bör ha referens till UI-element (dvs. inga knappar, textfält etc. i presentatören eller andra klasser).

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

Meddelandelogiken kan också kodas så här i 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);
        // }
        
    });
}

Gränssnittet måste refactored för att ta ActionEvent som en parametre:

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

Här behövs bara en meddelande-metod, den faktiska lyssnarmetoden och dess parameter överförs som parametrar. Om det behövs kan detta också användas för något som är lite mindre snyggt än faktiskt händelsehantering, allt fungerar så länge det finns en metod i gränssnittet, t.ex.

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

Presentatören kan ta in vyn och lägga till sig själv som lyssnare. När du klickar på knappen i vyn meddelar vyn alla lyssnare (inklusive presentatören). Nu när presentatören meddelas kan det vidta lämpliga åtgärder för att uppdatera modellen (dvs. applikationens tillstånd) och sedan uppdatera vyn i enlighet därmed.

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

För att sätta ihop allt kan vyn skapas och injiceras i presentatören. På samma sätt kan en initial modell skapas och injiceras. Även om båda kan skapas i presentatören, kan injektion av dem i konstruktören göra det enklare att testa.

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow