Home page arrow Articles arrow MVC (Model View Controller) by example

Interseting posts

Syndicate

Search

MVC (Model View Controller) by example

In this article I will show, using a straightforward example, how to implement and exploit the MVC framework (Model View Controller) in order to completely decouple the presentation from the logic of (possibly) any application. If you have any comment or doubt, please feel free to This e-mail address is being protected from spam bots, you need JavaScript enabled to view it .

  • testMVC.jar: this package contains the Java imlpementation described in the article

 

mvc.png

 

Thus, let's start from the following demo application: "A system which allow the user to start generic jobs and to track their progress. In this dummy application jobs do not  perform any operations apart waiting a couple of seconds to complete. The user has the possibility to:

  • see the current progress of completion of all jobs started
  • launch another job that will start immediately
  • close the application

For simplicity jobs do not require any input parameters and  the time required to complete is randomly calculated at creation time. Each specified time interval the job progress is incremented until it completes."

The model 

The model part of our system should represent the state of the application in any given instant. It should provide method to modify and read the states but not the logic on how to do that. Moreover it do not possess any instruction on how to display its variables.
Thus the model is independent from the user interface used and from the logic that modify the states. We may state that it is represented by the nodes of a finite state automata.
In my running example the system model is represented by a class called Model (surprising, isn't it?) 

/**The Model represents the state of the system in the current instant*/
public class Model [...] {

    [...]
   
    /**The model is represented by a set of working jobs*/
    private ArrayList<Job> array;
   
    public Model(){
        array = new ArrayList<Job>();
        [...]
    }

    /**add the given job to the state and notify all listeners*/
    public void addJob(Job j) {
        array.add(j);      
       [...]
    }

    public Collection<Job> getJobs() {
        return array;
    }

}

The class just contains an array of object Job and some methods to add and retrieve these instances. The class Job itself is quite simple:

public class Job extends javax.swing.DefaultBoundedRangeModel{
   
    /**used to count the instances of job generated */
    private static int counter = 0;
    private static final long serialVersionUID = 1L;
   
    /**instance number of this job*/
    private int idx;
   
    public Job(){
        super(0,0, 0, (int)Math.rint(Math.random()*1000));
        idx = ++counter;
    }

    /** @return true if the current value is equal to the maximum value*/
    public boolean isCompleted() {
        return super.getValue() >= super.getMaximum();
    }
     
    public String toString(){
        return "job " + idx + "(" + getValue() + "/" + getMaximum() + ")";
    }
   
 }

I've extended a model class taken directly from the Swing library. This is just a shortcut to write less code, since that model (BoundedRangeModel)  fits perfectly my needs. It is a class used to store the state of n object which have a minimum m,  a maximum M value and a current value V such as m <= V <= M. Thus it correctly represent a Job as it is described by our specification.

Adding a model listener 

In order to react to model state changes I will adopt the observer/observable pattern. In particular the model will represent the observable object and all interfaces will represent its observers. n particular each view will extends the following interface:

/**Listener to event thrown by the Model*/
public interface ModelListener {

    /** notification of a newly added job j*/
    public void notifyJobAdd(Job j);
   
    /** notification of the completion of job j*/
    public void notifyJobCompleted(Job j);
}

The two event that we are going to observe are the add of a newly created job and the completion of a previous job.
When these events happen, the model needs to notify all the registered observers. Thus the model need to expose a method addListener() which is used to register a new observer to it. Moreover in order to notify of a job completion the model need to observe each working job: the model itself is the observer of the observable job.
The complete Model class is represented by the following (where in addition you will find the code to manage the listeners): 

/**The Model represents the state of the system in the current instant*/
public class Model implements ChangeListener {

    /**Set of listeners which are registered to event thrown by this model*/
    HashSet<ModelListener> listeners = null;
   
    /**The model is represented by a set of working jobs*/
    private ArrayList<Job> array;
   
   
    public Model(){
        array = new ArrayList<Job>();
        listeners = new HashSet<ModelListener>();
    }

    /**add a listener to event thrown by this class*/
    public void addListener(ModelListener l){       
        listeners.add(l);
    }

    /**add the given job to the state and notify all listeners*/
    public void addJob(Job j) {
        array.add(j);
        j.addChangeListener(this); //this model listens to changes in each job state
       
        for(ModelListener l : listeners) l.notifyJobAdd(j);
    }

    /**notification of a change in one of the model jobs: the notification is forwarded to the model listener*/
    public void stateChanged(ChangeEvent e) {
        Job j = (Job)e.getSource(); //the job which threw the event
        if (j.isCompleted()) {
            for(ModelListener l : listeners) l.notifyJobCompleted(j);
        }
    }

    public Collection<Job> getJobs() {
        return array;
    }

}

The controller

The controller will represents the logic that modify the state of the system. It should provide method to perform actions required by the user, it should check the validity of such actions and perform any transitions from a state to the other. It represents the arcs of the finite state automata.

In our example the controller simply exposes a method to add a new job (addJobAction()). The jobs update, instead, is implemented using a concurrent thread for each working job. When a job is created and added to the model, a JobThread is associated to it; this thread simply keeps updating at regular intervals the job progress until it completes.

public class Controller {

    Model model;
   
    public Controller(Model m){
        this.model = m;
    }
   
    /**The controller is able to create a new job via this action*/
    public void createJobAction(){
        //create a new job
        Job j = new Job();
        //add it to the model (this will throw a notification to the UIs)
        model.addJob(j);
       
        //start a thread to update the job concurrently
        new JobThread(j).start();
    }
     
    /** A thread used to update each job concurrently to the other application threads */
    private class JobThread extends Thread{
       
        private Job j; //the job to manage
        public JobThread(Job j){
            this.j = j;
        }
       
        public void run(){
            //keep incrementing the job progress at each interation
            while(!j.isCompleted()){
                try {
                    int ct = j.getValue();
                    j.setValue(ct+1);
                    synchronized(this) {
                        wait(20); //wait some times from an update to the other
                    }
                } catch (InterruptedException e) {
                    //the thread has been woken up..
                }
               
            }
        }
    }
}

The view

The view represents a module which is able to read the current state and display it to the user. Moreover it receives input from the user and transforms them into action which are transmitted to the controller.
In my example I've implemented two different view: a textual user interface, which exploits System.out and System.in to interact with the user, and a graphical user interface, which uses Swing widgets to do the same. notice that both views requires to implement the ModelListener interface in order to react to model changes, and both requires the controller in order to perform actions.

Textual user interface

public class Tui implements ModelListener{

    private Model model;
    private Controller controller;

    public Tui(Model m, Controller c){
        this.model = m;
        this.controller = c;
    }

    /**Shows the menu on the console and WAIT for an input from the user. The method stops by closing the application*/
 
   public void display(){
        Scanner s = new Scanner(System.in);
        
        boolean  keepWorking = true;
        while (keepWorking){
            System.out.println("_______________________________________________________________________");
            System.out.println("what do you need dude?");
            System.out.println("1: \trefresh system state");
            System.out.println("2: \tadd a job");
            System.out.println("3: \tget out of here!");
            System.out.println("_______________________________________________________________________");
            
            int x  = s.nextInt(); //this is a blocking method...
            System.out.println();
            System.out.println();
            
            switch (x){ //check the user requests    
            case 1: //display the current status of the model
                    displayJobs();
                    break;
            case 2: //create a new job
                    controller.createJobAction();
                    break;
            case 3: //get out of the program
                    keepWorking = false;
                    System.exit(0);
                    break;
            }
        
        }

    }

    /**displays the status of the system in this istant*/
    private void displayJobs() {
        Collection<Job> c = model.getJobs();
        
        for(Job j : c){ //for each job output a descritpion line
            System.out.print(j + "\t");
            int x = 100*j.getValue() /j.getMaximum();
            String token = (x == 100)? "#" : "-";
            for (int i=0; i < x; i++) System.out.print(token);
            System.out.println();
        }
    }

    public void notifyJobAdd(Job j) {
        System.out.println(j + " was succesfully added and started");
        displayJobs();
    }

    public void notifyJobCompleted(Job j) {
        System.out.println(j + " has completed");        
    }
   
}

Graphical user interface 

 public class Gui extends JFrame implements ModelListener{

    private static final long serialVersionUID = 1L;

    private Controller controller;

    /**Correspondence between each Job and its related progress bar*/
    private HashMap<Job, JProgressBar> bars = new HashMap<Job, JProgressBar>();
    
    public Gui(Model m, Controller c){
        this.controller = c;
        setGUI();
    }

    private void setGUI(){
        //use a simple vertical box layout
        BoxLayout bl = new BoxLayout(this.getContentPane(), BoxLayout.PAGE_AXIS);
        this.getContentPane().setLayout(bl);
                
        this.setMinimumSize(new Dimension(600,400));
        
        JButton b = new JButton();
        b.setAction(new AddAction());
        b.setText("add job...");
        b.setAlignmentX(0.5f);
        this.add(b);
        
        this.pack();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }
      
    /**Action thrown when the add button is pressed*/
    private class AddAction extends AbstractAction{
        private static final long serialVersionUID = 1L;

        public void actionPerformed(ActionEvent e) {
            controller.createJobAction();
        }
    }
   
    /**notification from the model that a job has been added*/
    public void notifyJobAdd(Job j) {
        //create a new progress bar using j as the corresponent model
        JProgressBar bar = new JProgressBar(j);
        
        bar.setBorder(BorderFactory.createEmptyBorder(2, 2, 2, 2));
        bars.put(j, bar);
        
        this.getContentPane().add(bar);
        
        //revalidate the layout (there is a new item inserted)
        this.validate();
        this.pack();
        
    }

    /** displays in red the completed jobs*/
    public void notifyJobCompleted(Job j) {
        JProgressBar bar = bars.get(j);
        bar.setForeground(Color.RED);
        bar.repaint();
        
    }

    /**shows the window interface*/
    public void display() {
        SwingUtilities.invokeLater(new Runnable(){
            public void run(){
                setLocationByPlatform(true);
                setVisible(true);
            }
        });
      }
 }

 Both views implements a display method which is used to start the user interaction. Notice, however, that:

  • in the textual interface, this method is blocking, since it keeps looping in order to show a menu after each user requests and then wait for a user keyboard input
  • in the graphical interface, this method is non blocking, since it put a display request to the EventThread (setVisible(true)) and returns immediately.

Putting everything all thogether

Let's now create an overall class to instantiate and connect the components:

    public static void main(String... argv){
   
        //instantiate the model
        Model m  = new Model();
       
        //instantiate the controller which requires a model
        Controller c = new Controller(m);
       
        //instantiate the uis, which require model and controller
        Tui t  = new Tui(m, c);
        Gui g = new Gui(m, c);
       
       
        //register the uis as listeners of the model
        m.addListener(g);
        m.addListener(t);
       
       
        //perform the display
        g.display();
        t.display();

        //end this thread
    }

Notice in this code fragment that I have instantiated both a graphical user interface and a textual interface on the same model. The system still work perfectly allowing the user to control the state in two different ways at the same time!
The fragment is straightforward; at first I have instantiated a new model which is then fed to the controller, since it have to control it; then it's the turn of the two interfaces which are instantiated using both the controller and the model as input; finally I've performed the listener registration on the model of both interfaces in order to allow these to "listen" for changes in the system state. 

If you have any comment or doubt, please feel free to This e-mail address is being protected from spam bots, you need JavaScript enabled to view it




Share this article
Reddit!Del.icio.us!Slashdot!Technorati!StumbleUpon!Furl!Free social bookmarking plugins and extensions for Joomla! websites!
 
< Prev