Apply a “Separation of Concerns” principle in a way that allows developers to build and test user interfaces.
Real-world example
Consider File selection application that allows to select a file from storage. File selection logic is completely separated from user interface implementation.
In plain words
It separates the UI completely from service/domain layer into Presenter.
Wikipedia says
Model–view–presenter (MVP) is a derivation of the model–view–controller (MVC) architectural pattern, and is used mostly for building user interfaces.
Programmatic example
Let’s understand a simple file selection application build in AWT/Swing.
FileLoader
reads & loads contain of given file. It represents the model component of MVP.
1public class FileLoader implements Serializable {
2
3 /**
4 * Generated serial version UID.
5 */
6 private static final long serialVersionUID = -4745803872902019069L;
7
8 private static final Logger LOGGER = LoggerFactory.getLogger(FileLoader.class);
9
10 /**
11 * Indicates if the file is loaded or not.
12 */
13 private boolean loaded;
14
15 /**
16 * The name of the file that we want to load.
17 */
18 private String fileName;
19
20 /**
21 * Loads the data of the file specified.
22 */
23 public String loadData() {
24 var dataFileName = this.fileName;
25 try (var br = new BufferedReader(new FileReader(new File(dataFileName)))) {
26 var result = br.lines().collect(Collectors.joining("\n"));
27 this.loaded = true;
28 return result;
29 } catch (Exception e) {
30 LOGGER.error("File {} does not exist", dataFileName);
31 }
32
33 return null;
34 }
35
36 /**
37 * Sets the path of the file to be loaded, to the given value.
38 *
39 * @param fileName The path of the file to be loaded.
40 */
41 public void setFileName(String fileName) {
42 this.fileName = fileName;
43 }
44
45 /**
46 * Gets the path of the file to be loaded.
47 *
48 * @return fileName The path of the file to be loaded.
49 */
50 public String getFileName() {
51 return this.fileName;
52 }
53
54 /**
55 * Returns true if the given file exists.
56 *
57 * @return True, if the file given exists, false otherwise.
58 */
59 public boolean fileExists() {
60 return new File(this.fileName).exists();
61 }
62
63 /**
64 * Returns true if the given file is loaded.
65 *
66 * @return True, if the file is loaded, false otherwise.
67 */
68 public boolean isLoaded() {
69 return this.loaded;
70 }
71}
FileSelectorView
interface represents the View component in the MVP pattern. It can be
implemented by either the GUI components, or by the Stub. This is how it eases the UI testing.
1public interface FileSelectorView extends Serializable {
2
3 /**
4 * Opens the view.
5 */
6 void open();
7
8 /**
9 * Closes the view.
10 */
11 void close();
12
13 /**
14 * Returns true if view is opened.
15 *
16 * @return True, if the view is opened, false otherwise.
17 */
18 boolean isOpened();
19
20 /**
21 * Sets the presenter component, to the one given as parameter.
22 *
23 * @param presenter The new presenter component.
24 */
25 void setPresenter(FileSelectorPresenter presenter);
26
27 /**
28 * Gets presenter component.
29 *
30 * @return The presenter Component.
31 */
32 FileSelectorPresenter getPresenter();
33
34 /**
35 * Sets the file's name, to the value given as parameter.
36 *
37 * @param name The new name of the file.
38 */
39 void setFileName(String name);
40
41 /**
42 * Gets the name of file.
43 *
44 * @return The name of the file.
45 */
46 String getFileName();
47
48 /**
49 * Displays a message to the users.
50 *
51 * @param message The message to be displayed.
52 */
53 void showMessage(String message);
54
55 /**
56 * Displays the data to the view.
57 *
58 * @param data The data to be written.
59 */
60 void displayData(String data);
61}
FileSelectorJFrame
represents the GUI implementation of the View component in the MVP pattern.
1public class FileSelectorJFrame extends JFrame implements FileSelectorView, ActionListener {
2
3 /**
4 * Default serial version ID.
5 */
6 private static final long serialVersionUID = 1L;
7
8 /**
9 * The "OK" button for loading the file.
10 */
11 private final JButton ok;
12
13 /**
14 * The cancel button.
15 */
16 private final JButton cancel;
17
18 /**
19 * The text field for giving the name of the file that we want to open.
20 */
21 private final JTextField input;
22
23 /**
24 * A text area that will keep the contents of the file opened.
25 */
26 private final JTextArea area;
27
28 /**
29 * The Presenter component that the frame will interact with.
30 */
31 private FileSelectorPresenter presenter;
32
33 /**
34 * The name of the file that we want to read it's contents.
35 */
36 private String fileName;
37
38 /**
39 * Constructor.
40 */
41 public FileSelectorJFrame() {
42 super("File Loader");
43 this.setDefaultCloseOperation(EXIT_ON_CLOSE);
44 this.setLayout(null);
45 this.setBounds(100, 100, 500, 200);
46
47 /*
48 * Add the panel.
49 */
50 var panel = new JPanel();
51 panel.setLayout(null);
52 this.add(panel);
53 panel.setBounds(0, 0, 500, 200);
54 panel.setBackground(Color.LIGHT_GRAY);
55
56 /*
57 * Add the info label.
58 */
59 var info = new JLabel("File Name :");
60 panel.add(info);
61 info.setBounds(30, 10, 100, 30);
62
63 /*
64 * Add the contents label.
65 */
66 var contents = new JLabel("File contents :");
67 panel.add(contents);
68 contents.setBounds(30, 100, 120, 30);
69
70 /*
71 * Add the text field.
72 */
73 this.input = new JTextField(100);
74 panel.add(input);
75 this.input.setBounds(150, 15, 200, 20);
76
77 /*
78 * Add the text area.
79 */
80 this.area = new JTextArea(100, 100);
81 var pane = new JScrollPane(area);
82 pane.setHorizontalScrollBarPolicy(HORIZONTAL_SCROLLBAR_AS_NEEDED);
83 pane.setVerticalScrollBarPolicy(VERTICAL_SCROLLBAR_AS_NEEDED);
84 panel.add(pane);
85 this.area.setEditable(false);
86 pane.setBounds(150, 100, 250, 80);
87
88 /*
89 * Add the OK button.
90 */
91 this.ok = new JButton("OK");
92 panel.add(ok);
93 this.ok.setBounds(250, 50, 100, 25);
94 this.ok.addActionListener(this);
95
96 /*
97 * Add the cancel button.
98 */
99 this.cancel = new JButton("Cancel");
100 panel.add(this.cancel);
101 this.cancel.setBounds(380, 50, 100, 25);
102 this.cancel.addActionListener(this);
103
104 this.presenter = null;
105 this.fileName = null;
106 }
107
108 @Override
109 public void actionPerformed(ActionEvent e) {
110 if (this.ok.equals(e.getSource())) {
111 this.fileName = this.input.getText();
112 presenter.fileNameChanged();
113 presenter.confirmed();
114 } else if (this.cancel.equals(e.getSource())) {
115 presenter.cancelled();
116 }
117 }
118
119 @Override
120 public void open() {
121 this.setVisible(true);
122 }
123
124 @Override
125 public void close() {
126 this.dispose();
127 }
128
129 @Override
130 public boolean isOpened() {
131 return this.isVisible();
132 }
133
134 @Override
135 public void setPresenter(FileSelectorPresenter presenter) {
136 this.presenter = presenter;
137 }
138
139 @Override
140 public FileSelectorPresenter getPresenter() {
141 return this.presenter;
142 }
143
144 @Override
145 public void setFileName(String name) {
146 this.fileName = name;
147 }
148
149 @Override
150 public String getFileName() {
151 return this.fileName;
152 }
153
154 @Override
155 public void showMessage(String message) {
156 JOptionPane.showMessageDialog(null, message);
157 }
158
159 @Override
160 public void displayData(String data) {
161 this.area.setText(data);
162 }
163}
FileSelectorStub
is a stub that implements the View interface and it is useful when we want to test the reaction to
user events, such as mouse clicks etc.
1public class FileSelectorStub implements FileSelectorView {
2
3 /**
4 * Indicates whether or not the view is opened.
5 */
6 private boolean opened;
7
8 /**
9 * The presenter Component.
10 */
11 private FileSelectorPresenter presenter;
12
13 /**
14 * The current name of the file.
15 */
16 private String name;
17
18 /**
19 * Indicates the number of messages that were "displayed" to the user.
20 */
21 private int numOfMessageSent;
22
23 /**
24 * Indicates if the data of the file where displayed or not.
25 */
26 private boolean dataDisplayed;
27
28 /**
29 * Constructor.
30 */
31 public FileSelectorStub() {
32 this.opened = false;
33 this.presenter = null;
34 this.name = "";
35 this.numOfMessageSent = 0;
36 this.dataDisplayed = false;
37 }
38
39 @Override
40 public void open() {
41 this.opened = true;
42 }
43
44 @Override
45 public void setPresenter(FileSelectorPresenter presenter) {
46 this.presenter = presenter;
47 }
48
49 @Override
50 public boolean isOpened() {
51 return this.opened;
52 }
53
54 @Override
55 public FileSelectorPresenter getPresenter() {
56 return this.presenter;
57 }
58
59 @Override
60 public String getFileName() {
61 return this.name;
62 }
63
64 @Override
65 public void setFileName(String name) {
66 this.name = name;
67 }
68
69 @Override
70 public void showMessage(String message) {
71 this.numOfMessageSent++;
72 }
73
74 @Override
75 public void close() {
76 this.opened = false;
77 }
78
79 @Override
80 public void displayData(String data) {
81 this.dataDisplayed = true;
82 }
83
84 /**
85 * Returns the number of messages that were displayed to the user.
86 */
87 public int getMessagesSent() {
88 return this.numOfMessageSent;
89 }
90
91 /**
92 * Returns true, if the data were displayed.
93 *
94 * @return True if the data where displayed, false otherwise.
95 */
96 public boolean dataDisplayed() {
97 return this.dataDisplayed;
98 }
99}
FileSelectorPresenter
represents the Presenter component in the MVP pattern.
It is responsible for reacting to the user’s actions and update the View component.
1public class FileSelectorPresenter implements Serializable {
2
3 /**
4 * Generated serial version UID.
5 */
6 private static final long serialVersionUID = 1210314339075855074L;
7
8 /**
9 * The View component that the presenter interacts with.
10 */
11 private final FileSelectorView view;
12
13 /**
14 * The Model component that the presenter interacts with.
15 */
16 private FileLoader loader;
17
18 /**
19 * Constructor.
20 *
21 * @param view The view component that the presenter will interact with.
22 */
23 public FileSelectorPresenter(FileSelectorView view) {
24 this.view = view;
25 }
26
27 /**
28 * Sets the {@link FileLoader} object, to the value given as parameter.
29 *
30 * @param loader The new {@link FileLoader} object(the Model component).
31 */
32 public void setLoader(FileLoader loader) {
33 this.loader = loader;
34 }
35
36 /**
37 * Starts the presenter.
38 */
39 public void start() {
40 view.setPresenter(this);
41 view.open();
42 }
43
44 /**
45 * An "event" that fires when the name of the file to be loaded changes.
46 */
47 public void fileNameChanged() {
48 loader.setFileName(view.getFileName());
49 }
50
51 /**
52 * Ok button handler.
53 */
54 public void confirmed() {
55 if (loader.getFileName() == null || loader.getFileName().equals("")) {
56 view.showMessage("Please give the name of the file first!");
57 return;
58 }
59
60 if (loader.fileExists()) {
61 var data = loader.loadData();
62 view.displayData(data);
63 } else {
64 view.showMessage("The file specified does not exist.");
65 }
66 }
67
68 /**
69 * Cancels the file loading process.
70 */
71 public void cancelled() {
72 view.close();
73 }
74}
Below code reflects how we wire-up the Presenter & the View and the Presenter & the Model.
1 var loader = new FileLoader();
2 var frame = new FileSelectorJFrame();
3 var presenter = new FileSelectorPresenter(frame);
4 presenter.setLoader(loader);
5 presenter.start();
Use the Model-View-Presenter in any of the following situations