Intent: Decouple the logic (model) from it's presentation (view), usually a GUI
In the traditional MVC it is still considered a direct interaction between the View and the Model.
A different solution permits a complete decoupling between the View and the Model.
To study this solution I realized a little Java example.
I presented this little example to my friend Riccardo Govoni, and he built a complete framework around this idea and the usage of annotations. He also published an article about this framework on Java World.
Here I present my original “simple stupid” example that doesn't make use of annotations:
MVC.java
package mvc; public class MVC { public static void main(String[] args) { Model model = new Model(); View view = new View(); Controller controller = new Controller(model, view); } }
Model.java
package mvc; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.util.Observer; import javax.swing.Timer; public class Model { public static final int DEFAULT_PERIOD = 60; private Timer timer; private int period; private ObservableInt seconds = new ObservableInt(); private ObservableEvent askQuestion = new ObservableEvent(); public Model(){ period = DEFAULT_PERIOD; timer = new Timer(1000, new timerListener()); } private class timerListener implements ActionListener { public void actionPerformed(ActionEvent e) { seconds.increment(); if (seconds.get() >= period) { timer.stop(); askQuestion.notifyEvent(); seconds.set(0); } } } public void questionAnswer(boolean answer){ if (answer) timer.start(); } public void setTimer() { if(timer.isRunning()) { timer.stop(); } else { timer.start(); } } public int getSeconds(){ return seconds.get(); } public void setPeriod(int period){ this.period = period; } public void addSecondsObserver(Observer controller){ seconds.addObserver(controller); } public void addQuestionObserver(Observer controller){ askQuestion.addObserver(controller); } }
Controller.java
package mvc; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.util.Observable; import java.util.Observer; public class Controller { private View view; private Model model; Controller(Model model, View view){ this.view = view; view.addstrStpBtnListener(new StrStpBtnListener()); view.addSetPrdBtnListener(new SetPrdBtnListener()); view.addClosingListener(new ClosingListener()); view.setScnFld(0); view.setPrdFld(Model.DEFAULT_PERIOD); this.model = model; model.addSecondsObserver(new SecondsObserver()); model.addQuestionObserver(new QuestionObserver()); } class ClosingListener extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } class StrStpBtnListener implements ActionListener { public void actionPerformed(ActionEvent e) { model.setTimer(); } } class SetPrdBtnListener implements ActionListener { public void actionPerformed(ActionEvent e) { int period = view.getPrdFld(); model.setPeriod(period); } } class SecondsObserver implements Observer { public void update(Observable obs, Object arg) { view.setScnFld(model.getSeconds()); } } class QuestionObserver implements Observer { public void update(Observable obs, Object arg) { model.questionAnswer(view.question()); } } }
ObservableInt.java
package mvc; import java.util.Observable; public class ObservableInt extends Observable { private int value = 0; public ObservableInt() {} public ObservableInt(int startingValue) { value = startingValue; } public int get(){ return value; } public void set(int newValue){ if(newValue != value){ value = newValue; notifyChange(); } } public void increment(){ value++; notifyChange(); } // Notify the observers calling the update method. private void notifyChange(){ setChanged(); notifyObservers(new Integer(value)); } }
ObservableEvent.java
package mvc; import java.util.Observable; public class ObservableEvent extends Observable { public void notifyEvent(){ setChanged(); notifyObservers(); } }
View.java
package mvc; import java.awt.Container; import java.awt.GridBagConstraints; import java.awt.GridBagLayout; import java.awt.Insets; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JOptionPane; import javax.swing.JTextField; public class View extends JFrame { private JTextField scnFld = new JTextField(5); private JTextField prdFld = new JTextField(5); private JButton setPrdBtn = new JButton("Set"); private JButton strStpBtn = new JButton("Start/Stop"); public View(){ super("View"); // Container Container container = this.getContentPane(); // Layout GridBagLayout gbl = new GridBagLayout(); container.setLayout(gbl); GridBagConstraints format = new GridBagConstraints(); //(0,0) Seconds Label JLabel scnLbl = new JLabel("Seconds"); format = new GridBagConstraints(0,0, 1,1, 0.0,0.0, GridBagConstraints.CENTER,GridBagConstraints.BOTH, new Insets(0,0,0,0), 0,0); gbl.setConstraints(scnLbl, format); container.add(scnLbl); //(1,0) Seconds Filed scnFld.setEditable(false); format = new GridBagConstraints(1,0, 1,1, 0.0,0.0, GridBagConstraints.CENTER,GridBagConstraints.BOTH, new Insets(0,0,0,0), 0,0); gbl.setConstraints(scnFld, format); container.add(scnFld); //(2,0) Start/Stop Button format = new GridBagConstraints(2,0, 1,1, 0.0,0.0, GridBagConstraints.CENTER,GridBagConstraints.BOTH, new Insets(0,0,0,0), 0,0); gbl.setConstraints(strStpBtn, format); container.add(strStpBtn); //(0,1) Period Label JLabel prdLbl = new JLabel("Period"); format = new GridBagConstraints(0,1, 1,1, 0.0,0.0, GridBagConstraints.CENTER,GridBagConstraints.BOTH, new Insets(0,0,0,0), 0,0); gbl.setConstraints(prdLbl, format); container.add(prdLbl); //(1,1) Period Field format = new GridBagConstraints(1,1, 1,1, 0.0,0.0, GridBagConstraints.CENTER,GridBagConstraints.BOTH, new Insets(0,0,0,0), 0,0); gbl.setConstraints(prdFld, format); container.add(prdFld); //(2,1) Set Period Button format = new GridBagConstraints(2,1, 1,1, 0.0,0.0, GridBagConstraints.CENTER,GridBagConstraints.BOTH, new Insets(0,0,0,0), 0,0); gbl.setConstraints(setPrdBtn, format); container.add(setPrdBtn); // Show frame this.pack(); this.setResizable(false); this.setLocation(250,250); this.setVisible(true); } // Closing Window Listener public void addClosingListener(WindowAdapter wa){ this.addWindowListener(wa); } // Start/Stop Button Listener public void addstrStpBtnListener(ActionListener actLst) { strStpBtn.addActionListener(actLst); } // Set Period Button Listener public void addSetPrdBtnListener(ActionListener actLst) { setPrdBtn.addActionListener(actLst); } // Set seconds Field public void setScnFld(int sec){ scnFld.setText((new Integer(sec)).toString()); } // Set seconds Field public void setPrdFld(int period){ prdFld.setText((new Integer(period)).toString()); } // Get period Field public int getPrdFld() { return Integer.parseInt(prdFld.getText()); } // Question public boolean question(){ int confirmation = JOptionPane.showConfirmDialog(null, "Restart ?", "Information", JOptionPane.YES_NO_OPTION); if (confirmation == 0) return true; if (confirmation == 1) return false; return false; } }