Merge pull request #3 from pitsios-s/master

Model-View-Presenter pattern
This commit is contained in:
Ilkka Seppälä 2014-09-14 00:04:00 +03:00
commit d3fbc2e90b
12 changed files with 702 additions and 1 deletions

View File

@ -189,6 +189,15 @@
* a snapshot of an object's state must be saved so that it can be restored to that state later, and
* a direct interface to obtaining the state would expose implementation details and break the object's encapsulation
##Model-View-Presenter
**Intent:** Apply a "Separation of Concerns" principle in a way that allows developers to build and test user interfaces.
![alt text](https://github.com/pitsios-s/java-design-patterns/blob/master/model-view-presenter/etc/model-view-presenter.jpg "Model-View-Presenter")
**Applicability:** Use the Model-View-Presenter in any of the following situations
* when you want to improve the "Separation of Concerns" principle in presentation logic
* when a user interface development and testing is necessary.
##Observer
**Intent:** Define a one-to-many dependency between objects so that when one object changes state, all its dependents are notified and updated automatically.

View File

@ -0,0 +1,2 @@
Test line 1
Test line 2

Binary file not shown.

After

Width:  |  Height:  |  Size: 130 KiB

View File

@ -0,0 +1,28 @@
<?xml version="1.0"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.iluwatar</groupId>
<artifactId>java-design-patterns</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<groupId>com.iluwatar</groupId>
<artifactId>model-view-presenter</artifactId>
<version>1.0-SNAPSHOT</version>
<name>model-view-presenter</name>
<url>http://maven.apache.org</url>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.java.dev.swing-layout</groupId>
<artifactId>swing-layout</artifactId>
<version>1.0.2</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,80 @@
package com.iluwatar;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
/**
* Every instance of this class represents the Model component
* in the Model-View-Presenter architectural pattern.
*
* It is responsible for reading and loading the contents of a given file.
*/
public class FileLoader {
/**
* Indicates if the file is loaded or not.
*/
private boolean loaded = false;
/**
* The name of the file that we want to load.
*/
private String fileName;
/**
* Loads the data of the file specified.
*/
public String loadData() {
try {
BufferedReader br = new BufferedReader(new FileReader(new File(this.fileName)));
String text = "";
String line = "";
while( (line = br.readLine()) != null ) {
text += line + "\n";
}
this.loaded = true;
br.close();
return text;
}
catch(Exception e) {
e.printStackTrace();
}
return null;
}
/**
* Sets the path of the file to be loaded, to the given value.
*
* @param fileName The path of the file to be loaded.
*/
public void setFileName(String fileName) {
this.fileName = fileName;
}
/**
* @return fileName The path of the file to be loaded.
*/
public String getFileName() {
return this.fileName;
}
/**
* @return True, if the file given exists, false otherwise.
*/
public boolean fileExists() {
return new File(this.fileName).exists();
}
/**
* @return True, if the file is loaded, false otherwise.
*/
public boolean isLoaded() {
return this.loaded;
}
}

View File

@ -0,0 +1,200 @@
package com.iluwatar;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/**
* This class is the GUI implementation of the View component
* In the Model-View-Presenter pattern.
*/
public class FileSelectorJFrame extends JFrame implements FileSelectorView, ActionListener {
/**
* Default serial version ID.
*/
private static final long serialVersionUID = 1L;
/**
* The "OK" button for loading the file.
*/
private JButton OK;
/**
* The cancel button.
*/
private JButton cancel;
/**
* The information label.
*/
private JLabel info;
/**
* The contents label.
*/
private JLabel contents;
/**
* The text field for giving the name of the file
* that we want to open.
*/
private JTextField input;
/**
* A text area that will keep the contents of the file opened.
*/
private JTextArea area;
/**
* The panel that will hold our widgets.
*/
private JPanel panel;
/**
* The Presenter component that the frame will interact with
*/
private FileSelectorPresenter presenter;
/**
* The name of the file that we want to read it's contents.
*/
private String fileName;
/**
* Constructor.
*/
public FileSelectorJFrame() {
super("File Loader");
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
this.setLayout(null);
this.setBounds(100, 100, 500, 200);
/*
* Add the panel.
*/
this.panel = new JPanel();
panel.setLayout(null);
this.add(panel);
panel.setBounds(0, 0, 500, 200);
panel.setBackground(Color.LIGHT_GRAY);
/*
* Add the info label.
*/
this.info = new JLabel("File Name :");
this.panel.add(info);
info.setBounds(30, 10, 100, 30);
/*
* Add the contents label.
*/
this.contents = new JLabel("File contents :");
this.panel.add(contents);
this.contents.setBounds(30, 100, 120, 30);
/*
* Add the text field.
*/
this.input = new JTextField(100);
this.panel.add(input);
this.input.setBounds(150, 15, 200, 20);
/*
* Add the text area.
*/
this.area = new JTextArea(100, 100);
JScrollPane pane = new JScrollPane(area);
pane.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
pane.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED);
this.panel.add(pane);
this.area.setEditable(false);
pane.setBounds(150, 100, 250, 80);
/*
* Add the OK button.
*/
this.OK = new JButton("OK");
this.panel.add(OK);
this.OK.setBounds(250, 50, 100, 25);
this.OK.addActionListener(this);
/*
* Add the cancel button.
*/
this.cancel = new JButton("Cancel");
this.panel.add(this.cancel);
this.cancel.setBounds(380, 50, 100, 25);
this.cancel.addActionListener(this);
this.presenter = null;
this.fileName = null;
}
@Override
public void actionPerformed(ActionEvent e) {
if(e.getSource() == this.OK) {
this.fileName = this.input.getText();
presenter.fileNameChanged();
presenter.confirmed();
}
else if(e.getSource() == this.cancel) {
presenter.cancelled();
}
}
@Override
public void open() {
this.setVisible(true);
}
@Override
public void close() {
this.dispose();
}
@Override
public boolean isOpened() {
return this.isVisible();
}
@Override
public void setPresenter(FileSelectorPresenter presenter) {
this.presenter = presenter;
}
@Override
public FileSelectorPresenter getPresenter() {
return this.presenter;
}
@Override
public void setFileName(String name) {
this.fileName = name;
}
@Override
public String getFileName() {
return this.fileName;
}
@Override
public void showMessage(String message) {
JOptionPane.showMessageDialog(null, message);
}
@Override
public void displayData(String data) {
this.area.setText(data);
}
}

View File

@ -0,0 +1,76 @@
package com.iluwatar;
/**
* Every instance of this class represents the Presenter component
* in the Model-View-Presenter architectural pattern.
*
* It is responsible for reacting to the user's actions and update the View component.
*/
public class FileSelectorPresenter {
/**
* The View component that the presenter interacts with.
*/
private FileSelectorView view;
/**
* The Model component that the presenter interacts with.
*/
private FileLoader loader;
/**
* Constructor
*
* @param view The view component that the presenter will interact with.
*/
public FileSelectorPresenter(FileSelectorView view) {
this.view = view;
}
/**
* Sets the FileLoader object, to the value given as parameter.
*
* @param loader The new FileLoader object(the Model component).
*/
public void setLoader(FileLoader loader) {
this.loader = loader;
}
/**
* Starts the presenter.
*/
public void start() {
view.setPresenter(this);
view.open();
}
/**
* An "event" that fires when the name of the file to be loaded changes.
*/
public void fileNameChanged() {
loader.setFileName(view.getFileName());
}
public void confirmed() {
if(loader.getFileName() == null || loader.getFileName().equals("")) {
view.showMessage("Please give the name of the file first!");
return;
}
if(loader.fileExists()) {
String data = loader.loadData();
view.displayData(data);
}
else {
view.showMessage("The file specified does not exist.");
}
}
/**
* Cancels the file loading process.
*/
public void cancelled() {
view.close();
}
}

View File

@ -0,0 +1,109 @@
package com.iluwatar;
/**
* Every instance of this class represents the Stub component in
* the Model-View-Presenter architectural pattern.
*
* The stub implements the View interface and it is useful when
* we want the test the reaction to user events, such as mouse clicks.
*
* Since we can not test the GUI directly, the MVP pattern provides
* this functionality through the View's dummy implementation, the Stub.
*/
public class FileSelectorStub implements FileSelectorView {
/**
* Indicates whether or not the view is opened.
*/
private boolean opened;
/**
* The presenter Component.
*/
private FileSelectorPresenter presenter;
/**
* The current name of the file.
*/
private String name;
/**
* Indicates the number of messages that were "displayed" to the user.
*/
private int numOfMessageSent;
/**
* Indicates if the data of the file where displayed or not.
*/
private boolean dataDisplayed;
/**
* Constructor
*/
public FileSelectorStub() {
this.opened = false;
this.presenter = null;
this.name = "";
this.numOfMessageSent = 0;
this.dataDisplayed = false;
}
@Override
public void open() {
this.opened = true;
}
@Override
public void setPresenter(FileSelectorPresenter presenter) {
this.presenter = presenter;
}
@Override
public boolean isOpened() {
return this.opened;
}
@Override
public FileSelectorPresenter getPresenter() {
return this.presenter;
}
@Override
public String getFileName() {
return this.name;
}
@Override
public void setFileName(String name) {
this.name = name;
}
@Override
public void showMessage(String message) {
this.numOfMessageSent++;
}
@Override
public void close() {
this.opened = false;
}
@Override
public void displayData(String data) {
this.dataDisplayed = true;
}
/**
* Returns the number of messages that were displayed to the user.
*/
public int getMessagesSent() {
return this.numOfMessageSent;
}
/**
* @return True if the data where displayed, false otherwise.
*/
public boolean dataDisplayed() {
return this.dataDisplayed;
}
}

View File

@ -0,0 +1,62 @@
package com.iluwatar;
/**
* This interface represents the View component in the
* Model-View-Presenter pattern. It can be implemented
* by either the GUI components, or by the Stub.
*/
public interface FileSelectorView {
/**
* Opens the view.
*/
public void open();
/**
* Closes the view.
*/
public void close();
/**
* @return True, if the view is opened, false otherwise.
*/
public boolean isOpened();
/**
* Sets the presenter component, to the one given as parameter.
*
* @param presenter The new presenter component.
*/
public void setPresenter(FileSelectorPresenter presenter);
/**
* @return The presenter Component.
*/
public FileSelectorPresenter getPresenter();
/**
* Sets the file's name, to the value given as parameter.
*
* @param name The new name of the file.
*/
public void setFileName(String name);
/**
* @return The name of the file.
*/
public String getFileName();
/**
* Displays a message to the users.
*
* @param message The message to be displayed.
*/
public void showMessage(String message);
/**
* Displays the data to the view.
*
* @param data The data to be written.
*/
public void displayData(String data);
}

View File

@ -0,0 +1,12 @@
package com.iluwatar;
public class MainApp {
public static void main(String[] args) {
FileLoader loader = new FileLoader();
FileSelectorJFrame jFrame = new FileSelectorJFrame();
FileSelectorPresenter presenter = new FileSelectorPresenter(jFrame);
presenter.setLoader(loader);
presenter.start();
}
}

View File

@ -0,0 +1,122 @@
package com.iluwatar;
import static org.junit.Assert.*;
import org.junit.Before;
import org.junit.Test;
/**
* This test case is responsible for testing our application
* by taking advantage of the Model-View-Controller architectural pattern.
*/
public class FileSelectorPresenterTest {
/**
* The Presenter component.
*/
private FileSelectorPresenter presenter;
/**
* The View component, implemented this time as a Stub!!!
*/
private FileSelectorStub stub;
/**
* The Model component.
*/
private FileLoader loader;
/**
* Initializes the components of the test case.
*/
@Before
public void setUp() {
this.stub = new FileSelectorStub();
this.loader = new FileLoader();
presenter = new FileSelectorPresenter(this.stub);
presenter.setLoader(loader);
}
/**
* Tests if the Presenter was successfully connected with the View.
*/
@Test
public void wiring() {
presenter.start();
assertNotNull(stub.getPresenter());
assertTrue(stub.isOpened());
}
/**
* Tests if the name of the file changes.
*/
@Test
public void updateFileNameToLoader() {
String EXPECTED_FILE = "Stamatis";
stub.setFileName(EXPECTED_FILE);
presenter.start();
presenter.fileNameChanged();
assertEquals(EXPECTED_FILE, loader.getFileName());
}
/**
* Tests if we receive a confirmation when we attempt to open a file
* that it's name is null or an empty string.
*/
@Test
public void fileConfirmationWhenNameIsNull() {
stub.setFileName(null);
presenter.start();
presenter.fileNameChanged();
presenter.confirmed();
assertFalse(loader.isLoaded());
assertEquals(1, stub.getMessagesSent());
}
/**
* Tests if we receive a confirmation when we attempt to open a file
* that it doesn't exist.
*/
@Test
public void fileConfirmationWhenFileDoesNotExist() {
stub.setFileName("RandomName.txt");
presenter.start();
presenter.fileNameChanged();
presenter.confirmed();
assertFalse(loader.isLoaded());
assertEquals(1, stub.getMessagesSent());
}
/**
* Tests if we can open the file, when it exists.
*/
@Test
public void fileConfirmationWhenFileExists() {
stub.setFileName("etc/data/test.txt");
presenter.start();
presenter.fileNameChanged();
presenter.confirmed();
assertTrue(loader.isLoaded());
assertTrue(stub.dataDisplayed());
}
/**
* Tests if the view closes after cancellation.
*/
@Test
public void cancellation() {
presenter.start();
presenter.cancelled();
assertFalse(stub.isOpened());
}
}

View File

@ -36,6 +36,7 @@
<module>iterator</module>
<module>mediator</module>
<module>memento</module>
<module>model-view-presenter</module>
<module>observer</module>
<module>state</module>
<module>strategy</module>
@ -59,4 +60,4 @@
</plugins>
</build>
</project>
</project>