mardi 10 février 2015

Java MVC model for large scale GUI

I'm trying to make a good MVC model that I will use in a large scale GUI project. I want to respect the maximum of rules and guidelines, and be able to decorrelate every parts of the MVC. The code must be beautiful, testable and maintainable. I wrote a sample project to show you my model, what do you think about it ? Don't be gentle haha


The Launcher :



package example;

import javax.swing.SwingUtilities;

/**
* Test class.
*
* @author AwaX
* @date Feb 10, 2015
*/
public class Example_MVC {

public static void main (String[] args) {
SwingUtilities.invokeLater(new Runnable() {

@Override
public void run () {
final Model model = new Model();
final Controller controller = new Controller(model);
controller.showGui();
}
});
}
}


The data model :



package example;

import java.util.Observable;

/**
* Data model.
*
* @author AwaX
* @date Feb 10, 2015
*/
public class Model extends Observable {

private int counter;

/**
* Instanciate a default data model.
*/
public Model () {
super();
this.counter = 0;
}

/**
* Notify data model observers with new update.
*
* @param arg
* Allow to specify a parameter for the observer.
*/
public final void notifyChanged (Object... arg) {
setChanged();
if (arg.length == 0) {
notifyObservers();
} else if (arg.length == 1 && arg[0] != null) {
notifyObservers(arg[0]);
} else {
throw new IllegalArgumentException("Only one argument allowed");
}
clearChanged();
}

public void inc () {
this.counter++;
notifyChanged(this.counter);
}

public int getCounter () {
return counter;
}

public void setCounter (int c) {
this.counter = c;
notifyChanged(this.counter);
}
}


The Controller :



package example;

import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import example.View.ActionCallback;

/**
* UI controller.
*
* @author AwaX
* @date Feb 10, 2015
*/
public class Controller {

private final Model model;
private final View view;
private final ScheduledExecutorService scheduler;

/**
* Instanciate the controller.
*
* @param model
* Data model.
*/
public Controller (final Model model) {
this.model = model;
this.view = new View(model);
this.view.addListener(this);
this.model.addObserver(this.view);
this.scheduler = Executors.newScheduledThreadPool(1);
this.scheduler.scheduleAtFixedRate(new IncrementTask(), 0, 1000, TimeUnit.MILLISECONDS);
}

/**
* Show the GUI.
*/
public void showGui () {
this.view.showGui();
}

@ActionCallback (event = "action1")
public void action1 () {
System.out.println("# Action 1");
}

@ActionCallback (event = "action2")
public void action2 () {
System.out.println("# Action 2");
}

/**
* Increment the data model periodically.
*
* @author AwaX
* @date Feb 10, 2015
*/
private class IncrementTask implements Runnable {

@Override
public void run () {
model.inc();
}
}
}


The View :



package example;

import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Observable;
import java.util.Observer;

import javax.swing.AbstractAction;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JTextField;

/**
* User interface.
*
* @author AwaX
* @date Feb 10, 2015
*/
public class View extends JFrame implements Observer {

private static final long serialVersionUID = 4240474354742402647L;

@Retention (RetentionPolicy.RUNTIME)
@Target (ElementType.METHOD)
public @interface ActionCallback {
String event();
}

private final ArrayList<Object> listeners;

private JButton btn1;
private JButton btn2;
private JTextField tf;

/**
* Instanciate the user interface.
*
* @param model
* Data model.
*/
public View (final Model model) {
super("MVC Example");
this.listeners = new ArrayList<>();
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
createGui();
}

/**
* Show the GUI.
*/
public void showGui () {
setMinimumSize(new Dimension(300, 200));
pack();
setLocationRelativeTo(null);
setVisible(true);
}

/**
* Allow an object to subscribe for UI events.
*
* @param o
* The subscriber object.
*/
public void addListener (Object o) {
if (!this.listeners.contains(o)) {
this.listeners.add(o);
}
}

/**
* Create the GUI and add listeners.
*/
private void createGui () {
this.btn1 = new JButton("Button 1");
this.btn2 = new JButton("Button 2");
this.tf = new JTextField();

setLayout(new BoxLayout(getContentPane(), BoxLayout.PAGE_AXIS));
add(this.btn1);
add(this.btn2);
add(this.tf);

this.btn1.setAction(new ButtonAction("action1"));
this.btn2.setAction(new ButtonAction("action2"));
}

@Override
public void update (Observable o, Object arg) {
if (o instanceof Model) {
Model model = (Model) o;
this.tf.setText("" + model.getCounter());
}
}

/**
* Listener on button actions.
*
* @author AwaX
* @date Feb 10, 2015
*/
private class ButtonAction extends AbstractAction {

private static final long serialVersionUID = 9077483825287720181L;

public ButtonAction (final String name) {
super(name);
}

@Override
public void actionPerformed (ActionEvent e) {
for (Object o : listeners) {
for (Method m : o.getClass().getMethods()) {
for (Annotation a : m.getAnnotations()) {
if (a instanceof ActionCallback) {
ActionCallback callback = (ActionCallback) a;
if (callback.event().equals(e.getActionCommand())) {
try {
m.invoke(o);
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e1) {
e1.printStackTrace();
}
}
}
}
}
}
}
}
}





Aucun commentaire:

Enregistrer un commentaire