Java Notes

UI-Model Structure

Prev: Big Blob, Next: UI-Model communication

Separate the user interface from the "model". The single most important high-level structuring principle is to separate the user interface from the model (business logic, problem domain, ...). This provides a huge improvement in simplicity, altho at first it may not appear so. Program enhancements and maintenance are much easier with this structure.

Model doesn't know about user interface. The user interface creates an object of the model and calls model methods. The model never calls on the user interface, or even knows anything about the user interface.

Can the model represent itself in text or graphically? Yes. It is very common for a model class to override toString(), which creates a string representation of an object. This is, of course, something which is typically used by the user interface. Perhaps in the purest sense this is asking the model to implement something for the user interface. Note that even in this case the model doesn't know anything about the user interface.

It would be possible to implement the conversion to string in a non-model class which would interrogate the model to determine how to build the string. This is sometimes called a user-interface delegate. It's possible, but not often done. Java Swing does something like this so that different Look and Feels can be implemented.

A similar method for model classes to implement is draw(), which takes a graphics object to draw on. This is the graphical equivalent to toString().

MVC? A further separation is often proposed which additionally splits the user interface into a View (displaying information) and Controller (processing user interactions) -- the well-known MVC pattern. For most applications your programs will be simpler if you just separate the user interface (UI) from the model - the UI-Model (Presentation-Model or M-VC) pattern shown below.

Main class

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
// structure/presentation-model/CalcV3.java 
//   Calculator with separation of presentation and model.
// Fred Swartz -- December 2004

// Program Organization: Separate View+Controller and Model

import javax.swing.*;

public class CalcV3 {
    public static void main(String[] args) {     
        JFrame presentation = new CalcViewController();
        presentation.setVisible(true);
    }
}

The User Interface (Presentation, GUI, View+Controller, ...)

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
 50 
 51 
 52 
 53 
 54 
 55 
 56 
 57 
 58 
 59 
 60 
 61 
 62 
 63 
 64 
 65 
 66 
 67 
 68 
 69 
 70 
 71 
 72 
 73 
 74 
 75 
 76 
 77 
 78 
 79 
 80 
 81 
 82 
 83 
 84 
 85 
 86 
 87 
 88 
 89 
 90 
 91 
 92 
 93 
 94 
 95 
// structure/presentation-model/CalcViewController.java
// Fred Swartz - December 2004.

// GUI Organization - GUI independent of model.
// 
//     GUI subclasses JFrame and builds it in the constructor.
//
//     The GUI creates a calculator model object, but knows nothing 
//     about the internal implementation of the calculator.

import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class CalcViewController extends JFrame {
    //============================================================= Constants
    private static final String INITIAL_VALUE = "1";
    
    //========================================================= instance vars
    //... The Model.
    private CalcModel  m_logic;
    
    //... Components
    private JTextField m_userInputTf = new JTextField(5);
    private JTextField m_totalTf     = new JTextField(20);
    private JButton    m_multiplyBtn = new JButton("Multiply");
    private JButton    m_clearBtn    = new JButton("Clear");
    
    //=========================================================== constructor
    /** Constructor */
    CalcViewController() {
        //... Set up the logic
        m_logic = new CalcModel();
        m_logic.setValue(INITIAL_VALUE);
        
        //... Initialize components
        m_totalTf.setText(m_logic.getValue());
        m_totalTf.setEditable(false);
        
        //... Layout the components.        
        JPanel content = new JPanel();
        content.setLayout(new FlowLayout());
        content.add(new JLabel("Input"));
        content.add(m_userInputTf);
        content.add(m_multiplyBtn);
        content.add(new JLabel("Total"));
        content.add(m_totalTf);
        content.add(m_clearBtn);
        
        //... Add button listeners.
        m_multiplyBtn.addActionListener(new MultiplyListener());
        m_clearBtn.addActionListener(new ClearListener());
        
        //... finalize layout and set window parameters.
        this.setContentPane(content);
        this.pack();
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        this.setTitle("Simple Calc - Presentation-Model");
    }//end constructor
    
    
    ////////////////////////////////////////// inner class MultiplyListener
    /** When a multiplication is requested.
     *  1. Get the user input number.
     *  2. Call the model to mulitply by this number.
     *  3. Get the result from the Model.
     *  4. Set the Total textfield to this result.
     * If there was an error, display it in a JOptionPane.
     */
    class MultiplyListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            String userInput = "";
            try {
                userInput = m_userInputTf.getText();
                m_logic.multiplyBy(userInput);
                m_totalTf.setText(m_logic.getValue());
            } catch (NumberFormatException nfex) {
                JOptionPane.showMessageDialog(CalcViewController.this, 
                                      "Bad input: '" + userInput + "'");
            }
        }
    }//end inner class MultiplyListener
    
    
    //////////////////////////////////////////// inner class ClearListener
    /**  1. Reset model.
     *   2. Put model's value into Total textfield.
     */    
    class ClearListener implements ActionListener {
        public void actionPerformed(ActionEvent e) {
            m_logic.reset();
            m_totalTf.setText(m_logic.getValue());
        }
    }
}

The Model (Logic, ...)

  1 
  2 
  3 
  4 
  5 
  6 
  7 
  8 
  9 
 10 
 11 
 12 
 13 
 14 
 15 
 16 
 17 
 18 
 19 
 20 
 21 
 22 
 23 
 24 
 25 
 26 
 27 
 28 
 29 
 30 
 31 
 32 
 33 
 34 
 35 
 36 
 37 
 38 
 39 
 40 
 41 
 42 
 43 
 44 
 45 
 46 
 47 
 48 
 49 
// structure/presentation-model/CalcModel.java
// Fred Swartz - December 2004
// Model
//     This model is completely independent of the user interface.
//     It could as easily be used by a command line or web interface.

import java.math.BigInteger;

public class CalcModel {
    //... Constants
    private static final String INITIAL_VALUE = "0";
    
    //... Member variable defining state of calculator.
    private BigInteger m_total;  // The total current value state.
    
    //========================================================== constructor
    /** Constructor */
    public CalcModel() {
        reset();
    }
    
    //================================================================ reset
    /** Reset to initial value. */
    public void reset() {
        m_total = new BigInteger(INITIAL_VALUE);
    }
    
    //=========================================================== multiplyBy
    /** Multiply current total by a number.
    *@param operand Number (as string) to multiply total by.
    */
    public void multiplyBy(String operand) {
        m_total = m_total.multiply(new BigInteger(operand));
    }
    
    //============================================================= setValue
    /** Set the total value. 
    *@param value New value that should be used for the calculator total. 
    */
    public void setValue(String value) {
        m_total = new BigInteger(value);
    }
    
    //============================================================= getValue
    /** Return current calculator total. */
    public String getValue() {
        return m_total.toString();
    }
}