MVC

Model–View–Controller (MVC) separates the representation of information (a model) from the user's view of the information (the view) and the user's interaction with the information (the controller). The central idea behind MVC is code reusability and separation of responsibility.

Model
The model consists of application data and business rules. The model will provide setter and getter methods to access its data.
View
A view consists of the output on a display device. Multiple views of the same data (model) are possible, such as a pie chart for management and a tabular view for accountants.
Controller
The controller takes user interaction with the view and converts it to commands for the model.
The controller takes the state of the model and uses it to determine which components are to be available for the user to use in the view.

MVC Design

 

Model
The model cannot access either the view or the controller. Instead a model can notify Observers (views and controllers) of changes to its state. To receive notifications, views and controllers must register with the model to become Observers to the model. A model can notify may views and controllers simultanously of changes to its state. As the model does not access either the view or the controller, the model can now be used with more than one view/controller.
View
A view is only used to display a model's data. The view has no other functionality. It does not manipulate data and it does not manipulate its own components.

When the model changes state, it sends out a notification. The view must be an Observer of the model to receive this notification. Once it has been notified of a state change by the model, a view can access the model to request data (using getter methods) that it needs from the model to generate a new output display.
Controller
Each view will have its own controller. A view's controller will look after the various interactions that the user performs with the view. To do this, all of the view's event listening functionality will be dealt with by its controller. Depending on a user's interactions with a view, its controller might be requested to change the state of the model. A controller can access a model to change the model's data (using setter methods).

A view's controller will also ensure that the available set of commands in the view is appropriate for the current data being displayed on the view. To do this, the controller must be an Observer of the model. Once it has been notified of a state change by the model, a controller can directly access its view's components to enable or disable them.

MVC Template

The code below allows us to change our Applet template so that it is MCV ready.

We create a model and a controller at the top level of our program. The controller will create a view. The controller's view will be used in the setContentPane() method.

        Model model = new Model();
        Controller controller = new Controller(model);
        setContentPane(controller.this.view);

 

The model extends Observable. This means that the model can notify Observers of a change in its state.

    public class Model extends Observable

 

The controller takes in the model as part of its constructor. This gives the controller the ability to use the model's setter methods.

The controller creates a view.

The model adds the view as an observer. This means that the view can be notified of changes to the model.

        public Controller(Model model)
        {
            this.model = model;
            this.view = new View(this, this.model);
            this.model.addObserver(this.view);
        }

 

The view implements Observer. This means that it can listen for notifications from the model.

    public class View extends JPanel implements Observer

 

Because the view implements Observer, it must override the method update(). It will use this method to change its display, based on changes to the model's data.

@Override
        public void update(Observable o, Object o1)
        {
            // Fired by the Model
        }

 

The view takes in the controller and the model as part of its constructor. This means that the view can pass actions to the controller and access the model's getter methods.

public View(Controller controller, Model model)

 

MVC_Template_Demo Example: (Run Applet)

import java.util.Observable;
import java.util.Observer;
import javax.swing.*;

public class MVC_Template_Demo extends JApplet
{
    @Override
    public void init()
    {
        Model model = new Model();
        Controller controller = new Controller(model);
        this.setContentPane(controller.view);
    }

    public class Model extends Observable
    {
        public Model()
        {
        }
    }

    public class Controller
    {
        Model model;
        View view;

        public Controller(Model model)
        {
            this.model = model;
            this.view = new View(this, this.model);
            this.model.addObserver(this.view);
        }
    }

    public class View extends JPanel implements Observer
    {
        Controller controller;
        Model model;

        public View(Controller controller, Model model)
        {
            super();
            this.controller = controller;
            this.model = model;

            // Create GUI
            this.add(new JLabel("Hello World"));
        }

        @Override
        public void update(Observable o, Object o1)
        {
            // Fired by the Model
        }
    }
}

Basic Example

The example below allows a user to press a button. Once pressed, the button's actionPerformed() method passes the processing of the action to the controller.

The controller calls a model setter method to change the model's state. The controller sets the new state of the view's compents. In this case, it disables the button.

When the model changes its state, if sends out a notification to its Observers. As the view is an Observer of the model, its update() method is called. The view's update() method uses a model getter method to change the value of the displayed message.

MVC_Simple_Demo Example: (Run Applet)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.*;

public class MVC_Simple_Demo extends JApplet
{
    @Override
    public void init()
    {
        Model model = new Model();
        Controller controller = new Controller(model);
        setContentPane(controller.view);
    }

    public class Model extends Observable
    {
        String message;

        public Model()
        {
        }

        public void setMessage(String message)
        {
            this.message = message;

            // These two lines of code will fire the Observer's update() method
            // We need to do this every time we change data in the Model
            this.setChanged();
            this.notifyObservers();
        }

        public String getMessage()
        {
            return this.message;
        }
    }

    public class Controller
    {
        Model model;
        View view;

        public Controller(Model model)
        {
            this.model = model;
            this.view = new View(this, this.model);

            // Make the View be an Observer to the Model
            this.model.addObserver(this.view);
        }

        public void setMessage(String message)
        {
            this.model.setMessage(message);
            this.view.button.setEnabled(false);
        }
    }

    public class View extends JPanel implements Observer, ActionListener
    {
        Controller controller;
        Model model;
        final JButton button = new JButton("Press Me");
        final JLabel label = new JLabel("Old Message");

        public View(Controller controller, Model model)
        {
            super();
            this.controller = controller;
            this.model = model;

            // Create GUI

            this.add(this.button);
            this.add(this.label);

            this.button.addActionListener(this);
        }

        @Override
        public void update(Observable o, Object o1)
        {
            // Fired by the Model
            this.label.setText(this.model.getMessage());
        }

        @Override
        public void actionPerformed(ActionEvent ae)
        {
            this.controller.setMessage("New Message");
        }
    }
}

Customising the Observer update() method

All of the java listeners use the Observable/Observer pattern. However, instead of providing an update() method, they provide a customised method. For example, the ActionListener provides the actionPerformed() method.

Any customised Observable class will need to provide a way to keep track of its Observers. The interface MyObservable is used to force the programmer to provide code to do this task.

    public interface MyObservable
    {
         public void addObserver(MyObserver observer);
         public void deleteObserver(MyObserver observer);
         public void deleteObservers();
         public void notifyObservers();
    }

 

Once the model implements the MyObservable, the programmer will be forced to override the four methods above.

    public class Model implements MyObservable

 

The model keeps an arrayList of its Observers using the this.observers member variable.

        ArrayList<MyObserver> this.observers = new ArrayList();

 

The MyObserver interface is used to provide the custom named method to replace update(). In the example below, the custom name is called myUpdate().

    public interface MyObserver
    {
       public void myUpdate();
    }

 

MVC_CustomUpdate_Demo Example: (Run Applet)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.*;

public class MVC_CustomUpdate_Demo extends JApplet
{
    @Override
    public void init()
    {
        final Model model = new Model();
        final Controller controller = new Controller(model);
        this.setContentPane(controller.view);
    }

    public interface MyObservable
    {
        public void addObserver(MyObserver observer);
        public void deleteObserver(MyObserver observer);
        public void deleteObservers();
        public void notifyObservers();
    }

    public class Model implements MyObservable
    {
        private final ArrayList<MyObserver> observers = new ArrayList();
        private String message;

        public Model()
        {
        }

        @Override
        public void addObserver(MyObserver observer)
        {
            this.observers.add(observer);
        }

        @Override
        public void deleteObserver(MyObserver observer)
        {
            this.observers.remove(observer);

        }

        @Override
        public void deleteObservers()
        {
            this.observers.clear();
        }

        @Override
        public void notifyObservers()
        {
            for (int i = 0; i < this.observers.size(); i++)
            {
                final MyObserver observer = this.observers.get(i);
                observer.myUpdate();
            }
        }

        public void setMessage(String message)
        {
            this.message = message;
            this.notifyObservers();
        }

        public String getMessage()
        {
            return this.message;
        }
    }

    public class Controller
    {
        private Model model;
        private View view;

        public Controller(Model model)
        {
            this.model = model;
            this.view = new View(this, this.model);

            // Make the View be an Observer to the Model
            this.model.addObserver(this.view);
        }

        public void setMessage(String message)
        {
            this.model.setMessage(message);
        }
    }

    public interface MyObserver
    {
        public void myUpdate();
    }

    public class View extends JPanel implements MyObserver, ActionListener
    {
        private Controller controller;
        private Model model;
        private final JButton button = new JButton("Press Me");
        private final JLabel label = new JLabel("Old Message");

        public View(Controller controller, Model model)
        {
            super();
            this.controller = controller;
            this.model = model;

            // Create GUI

            this.add(this.button);
            this.add(this.label);

            this.button.addActionListener(this);
        }

        @Override
        public void myUpdate()
        {
            // Fired by the Model
            this.label.setText(this.model.getMessage());
        }

        @Override
        public void actionPerformed(ActionEvent ae)
        {
            this.button.setEnabled(false);
            this.controller.setMessage("New Message");
        }
    }
}

Moving the Listening code to the Controller

The view code is cleaner if the view's listening code is moved out of the view class. Then the view's only role is to display. It no longer needs to care about what actions it is performing. The example below moves the listening code into the controller.

MVC_ControllerListens_Demo Example: (Run Applet)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.*;

public class MVC_ControllerListens_Demo extends JApplet
{
    @Override
    public void init()
    {
        final Model model = new Model();
        final Controller controller = new Controller(model);
        this.setContentPane(controller.view);
    }

    public class Model extends Observable
    {
        private String message;

        public Model()
        {
        }

        public void setMessage(String message)
        {
            this.message = message;

            // These two lines of code will fire the Observer's update() method
            // We need to do this every time we change data in the Model
            this.setChanged();
            this.notifyObservers();
        }

        public String getMessage()
        {
            return this.message;
        }
    }

    public class Controller implements ActionListener
    {
        private Model model;
        private View view;

        public Controller(Model model)
        {
            this.model = model;
            this.view = new View(this, this.model);

            // Make the View be an Observer to the Model
            this.model.addObserver(this.view);
        }

        public void setMessage(String message)
        {
            this.model.setMessage(message);
        }

        // Controller is now implementing the ActionLister
        @Override
        public void actionPerformed(ActionEvent ae)
        {
            this.setMessage("New Message");
            this.view.button.setEnabled(false);
        }
    }

    public class View extends JPanel implements Observer
    {
        private Controller controller;
        private Model model;
        private final JButton button = new JButton("Press Me");
        private final JLabel label = new JLabel("Old Message");

        public View(Controller controller, Model model)
        {
            super();
            this.controller = controller;
            this.model = model;

            // Create GUI
            this.add(this.button);
            this.add(this.label);

            // The ActionListener is now moved to the Controller
            this.button.addActionListener(this.controller);
        }

        @Override
        public void update(Observable o, Object o1)
        {
            // Fired by the Model
            this.label.setText(this.model.getMessage());
        }
    }
}

Making the Controller be an Observer

The controller can be made into an Observer of the model. This means that the view and the controller are both informed of changes to the model's data. The controller can use its update() method to change the state of the view's components. In the example below, the controller's update() method disables the view's button.

MVC_ControllerListens_Demo Example: (Run Applet)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.*;

public class MVC_ControllerObserver_Demo extends JApplet
{
    @Override
    public void init()
    {
        final Model model = new Model();
        final Controller controller = new Controller(model);
        this.setContentPane(controller.view);
    }

    public class Model extends Observable
    {
        private String message;

        public Model()
        {
        }

        public void setMessage(String message)
        {
            this.message = message;

            // These two lines of code will fire the Observer's update() method
            // We need to do this every time we change data in the Model
            this.setChanged();
            this.notifyObservers();
        }

        public String getMessage()
        {
            return this.message;
        }
    }

    public class Controller implements ActionListener, Observer
    {
        private Model model;
        private View view;

        public Controller(Model model)
        {
            this.model = model;
            this.view = new View(this, this.model);

            // Make the View be an Observer to the Model
            this.model.addObserver(this.view);
            this.model.addObserver(this);
        }

        public void setMessage(String message)
        {
            this.model.setMessage(message);
        }

        // Controller is now implementing the ActionLister
        @Override
        public void actionPerformed(ActionEvent ae)
        {
            this.setMessage("New Message");
        }

        @Override
        public void update(Observable o, Object o1)
        {
            // Set the state of the components in the view
            // This will usually depend on the data state in the model
            if (this.view.button.isEnabled())
            {
                this.view.button.setEnabled(false);
            }
        }
    }

    public class View extends JPanel implements Observer
    {
        private Controller controller;
        private Model model;
        private JButton button = new JButton("Press Me");
        private JLabel label = new JLabel("Old Message");

        public View(Controller controller, Model model)
        {
            super();
            this.controller = controller;
            this.model = model;

            // Create GUI
            this.add(this.button);
            this.add(this.label);

            // The ActionListener is now moved to the Controller
            this.button.addActionListener(this.controller);
        }

        @Override
        public void update(Observable o, Object o1)
        {
            // Fired by the Model
            this.label.setText(this.model.getMessage());
        }
    }
}

Using the Controller To update the View's display

Once the controller is an Observer of the model, it can be used to update() the view. This means that the view is only used to set up the initial display. In the example below, the controller's update() method disables the view's button and diaplays the new message.

As the controller has a reference to the view, it can access the view's member variables, as shown below.

            this.view.this.label.setText(this.model.getMessage());

The view no longer needs to be an Observer, as it no longer accesses the model's getter methods.

The view is now totally de-coupled from the model.

MVC_No_View_Observer_Demo Example: (Run Applet)

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Observable;
import java.util.Observer;
import javax.swing.*;

public class MVC_No_View_Observer_Demo extends JApplet
{
    @Override
    public void init()
    {
        final Model model = new Model();
        final Controller controller = new Controller(model);
        this.setContentPane(controller.view);
    }

    public class Model extends Observable
    {
        private String message;

        public Model()
        {
        }

        public void setMessage(String message)
        {
            this.message = message;

            // These two lines of code will fire the Observer's update() method
            // We need to do this every time we change data in the Model
            this.setChanged();
            this.notifyObservers();
        }

        public String getMessage()
        {
            return this.message;
        }
    }

    public class Controller implements ActionListener, Observer
    {
        private Model model;
        private View view;

        public Controller(Model model)
        {
            this.model = model;
            this.view = new View(this);

            // Make the View be an Observer to the Model
            this.model.addObserver(this);
        }

        public void setMessage(String message)
        {
            this.model.setMessage(message);
        }

        // Controller is now implementing the ActionLister
        @Override
        public void actionPerformed(ActionEvent ae)
        {
            this.setMessage("New Message");
        }

        @Override
        public void update(Observable o, Object o1)
        {
            // Set the state of the components in the view
            // This will usually depend on the data state in the model
            if (this.view.button.isEnabled())
            {
                // set the state of the view's components
                this.view.button.setEnabled(false);

                // display the model's data
                this.view.label.setText(this.model.getMessage());
            }
        }
    }

    public class View extends JPanel
    {
        private Controller controller;
        private final JButton button = new JButton("Press Me");
        private final JLabel label = new JLabel("Old Message");

        public View(Controller controller)
        {
            super();
            this.controller = controller;

            // Create GUI
            this.add(this.button);
            this.add(this.label);

            // The ActionListener is now moved to the Controller
            this.button.addActionListener(this.controller);
        }
    }
}

A Complete Example

The example below ties all of the above example together. It includes:

MVC_Records_Demo Example: (Run Applet)

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.GridLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import javax.swing.*;

public class MVC_Records_Demo extends JApplet
{
    enum ViewState  // used to let the View know to display one or all records
    {
        VIEW_ONE_RECORD,
        VIEW_ALL_RECORDS
    }

    @Override
    public void init()
    {
        final PersonModel model = new PersonModel();
        final PersonController controller = new PersonController(model);
        this.setContentPane(controller.view);

        // initialise some data
        model.addPerson(new Person("Name A", "Address A", "Telephone A"));
        model.addPerson(new Person("Name B", "Address B", "Telephone B"));
        model.addPerson(new Person("Name C", "Address C", "Telephone C"));
        model.addPerson(new Person("Name D", "Address D", "Telephone D"));
        model.addPerson(new Person("Name E", "Address E", "Telephone E"));
    }

    public class Person
    {
        private String name;
        private String address;
        private String telephone;

        public Person(String name, String address, String telephone)
        {
            this.name = name;
            this.address = address;
            this.telephone = telephone;
        }

        public String getName()
        {
            return this.name;
        }

        public String getAddress()
        {
            return this.address;
        }

        public String getTelephone()
        {
            return this.telephone;
        }
    }

    public interface PersonModelInterface
    {
        public void addObserver(PersonObserver observer);
        public void deleteObserver(PersonObserver observer);
        public void deleteObservers();
        public void notifyObservers();
    }

    public class PersonModel implements PersonModelInterface
    {
        private final ArrayList<PersonObserver> observers = new ArrayList();
        private final ArrayList<Person> people = new ArrayList();   // the data

        public PersonModel()
        {
        }

        @Override
        public void addObserver(PersonObserver observer)
        {
            this.observers.add(observer);
        }

        @Override
        public void deleteObserver(PersonObserver observer)
        {
            this.observers.remove(observer);
        }

        @Override
        public void deleteObservers()
        {
            this.observers.clear();
        }

        @Override
        public void notifyObservers()
        {
            for (int i = 0; i < this.observers.size(); i++)
            {
                final PersonObserver observer = this.observers.get(i);
                observer.personUpdate();
            }
        }

        public int getSize()
        {
            return this.people.size();
        }

        public boolean isEmpty()
        {
            return this.people.isEmpty();
        }

        public void addPerson(Person person)
        {
            this.people.add(person);
            this.notifyObservers();
        }

        public Person getPerson(int index)
        {
            return this.people.get(index);
        }

        public void removePerson(int index)
        {
            if ((index < this.people.size()) && (index >= 0))
            {
                this.people.remove(index);
                this.notifyObservers();
            }
        }

        public void refreshData()
        {
            this.notifyObservers();
        }
    }

    public interface PersonObserver
    {
        public void personUpdate();
    }

    public class PersonController implements PersonObserver, ActionListener
    {
        private PersonModel model;
        private PersonView view;
        private ViewState state = ViewState.VIEW_ALL_RECORDS;  // set all records to be selected at start
        private int currentPosition;

        public PersonController(PersonModel model)
        {
            this.model = model;
            this.view = new PersonView(this);
            this.model.addObserver(this);
        }

        @Override
        public void personUpdate()
        {
            this.updateViewControls();
            this.updateViewDisplay();
        }

        private void updateViewControls()
        {
            // helper method called by the controller's updatePerson() method
            // update the view's display
            // The only role of this method is to adjust the view's controls (JButtons, etc)
            // to reflect the current state of the model's data
            // Enable and disable appropriate buttons, depending on which record is selected
            if (this.model.getSize() == 0)
            {
                this.view.first.setEnabled(false);
                this.view.previous.setEnabled(false);
                this.view.next.setEnabled(false);
                this.view.last.setEnabled(false);
                this.view.all.setEnabled(false);
            }
            else
            {
                this.view.first.setEnabled(true);
                this.view.previous.setEnabled(true);
                this.view.next.setEnabled(true);
                this.view.last.setEnabled(true);
                this.view.all.setEnabled(true);

                if (this.state == ViewState.VIEW_ALL_RECORDS)
                {
                    this.view.previous.setEnabled(false);
                    this.view.next.setEnabled(false);
                    this.view.all.setEnabled(false);
                }
                else
                {
                    if (this.currentPosition == 0)
                    {
                        this.view.first.setEnabled(false);
                        this.view.previous.setEnabled(false);
                    }
                    else if (this.currentPosition == (this.model.getSize() - 1))
                    {
                        this.view.last.setEnabled(false);
                        this.view.next.setEnabled(false);
                    }
                }
            }
        }

        private void updateViewDisplay()
        {
            // helper method called by the controller's updatePerson() method
            // update the view's controls
            // The only role of this method is to display the selected record or all records
            if (this.currentPosition >= this.model.getSize())
            {
                this.currentPosition = this.model.getSize() - 1;
            }

            if (!isEmpty())  // do not process an empty list
            {
                int fromRecord;
                int toRecord;

                if (getState() == ViewState.VIEW_ALL_RECORDS)
                {
                    fromRecord = 0;
                    toRecord = this.model.getSize() - 1;
                }
                else // only display the current record
                {
                    fromRecord = toRecord = this.currentPosition;
                }

                String output = "";
                for (int i = fromRecord; i <= toRecord; i++)
                {
                    output += (String) (this.model.getPerson(i).getName() + "\t" +
                                        this.model.getPerson(i).getAddress() + "\t" +
                                        this.model.getPerson(i).getTelephone());

                    if (i < toRecord)
                    {
                        output += "\n";
                    }
                }
                this.view.output.setText(output);
            }
            else
            {
                this.view.output.setText("");
            }
        }

        @Override
        public void actionPerformed(ActionEvent e)
        {
            final Object source = e.getSource();
            if (source.equals(this.view.first))
            {
                this.getFirst();
            }
            else if (source.equals(this.view.previous))
            {
                this.getPrevious();
            }
            else if (source.equals(this.view.next))
            {
                this.getNext();
            }
            else if (source.equals(this.view.last))
            {
                this.getLast();
            }
            else if (source.equals(this.view.all))
            {
                this.getAll();
            }
        }

        public boolean isEmpty()
        {
            return (this.model.isEmpty());
        }

        public ViewState getState()
        {
            return this.state;
        }

        public void getFirst()
        {
            this.state = ViewState.VIEW_ONE_RECORD;

            this.currentPosition = 0;
            this.model.refreshData();
        }

        public void getPrevious()
        {
            this.state = ViewState.VIEW_ONE_RECORD;

            if (this.currentPosition > 0)
            {
                this.currentPosition--;
            }

            this.model.refreshData();
        }

        public void getNext()
        {
            this.state = ViewState.VIEW_ONE_RECORD;

            if (this.currentPosition < (this.model.getSize() - 1))
            {
                this.currentPosition++;
            }
            this.model.refreshData();
        }

        public void getLast()
        {
            this.state = ViewState.VIEW_ONE_RECORD;

            this.currentPosition = this.model.getSize() - 1;
            this.model.refreshData();
        }

        public void getAll()
        {
            this.state = ViewState.VIEW_ALL_RECORDS;

            if (this.model.getSize() > 0)
            {
                this.currentPosition = 0;
            }
            this.model.refreshData();
        }
    }

    public class PersonView extends JPanel
    {
        private PersonController controller;
        private final JButton first = new JButton("First");
        private final JButton previous = new JButton("Previous");
        private final JButton next = new JButton("Next");
        private final JButton last = new JButton("Last");
        private final JButton all = new JButton("All");
        private final JTextArea output = new JTextArea();

        public PersonView(PersonController controller)
        {
            super();
            this.controller = controller;

            // Create GUI
            this.output.setEditable(false);
            this.output.setBackground(Color.white);
            JPanel controlPanel = new JPanel();

            controlPanel.setLayout(new GridLayout(1, 5));
            controlPanel.add(this.first);
            controlPanel.add(this.previous);
            controlPanel.add(this.next);
            controlPanel.add(this.last);
            controlPanel.add(this.all);

            setLayout(new BorderLayout());
            add("North", controlPanel);
            add("Center", this.output);

            // Add listeners
            this.first.addActionListener(this.controller);
            this.previous.addActionListener(this.controller);
            this.next.addActionListener(this.controller);
            this.last.addActionListener(this.controller);
            this.all.addActionListener(this.controller);

            // As all records are shown at the start, disable the previous, next and all buttons
            this.previous.setEnabled(false);
            this.next.setEnabled(false);
            this.all.setEnabled(false);
        }
    }
}
 
<div align="center"><a href="../versionC/index.html" title="DKIT Lecture notes homepage for Derek O&#39; Reilly, Dundalk Institute of Technology (DKIT), Dundalk, County Louth, Ireland. Copyright Derek O&#39; Reilly, DKIT." target="_parent" style='font-size:0;color:white;background-color:white'>&nbsp;</a></div>