MVC Exercise - Implementation

6/9/00 - Go Back


Here's the code for the 6/8/00 JSL meeting. We did this using the Use Cases Stepped Out and Model, one Implementation Plan step at a time. We stressed that the implementation is trivial if prior process steps are well done. If you study the code, you'll see that indeed it's trivial - it's the total process and best practices that we are really learning here. To make running all this easier, here's the code zip file.

(NOTE - In progress, there's a small bug due to confusion about door numbers being zero or one based. We need to first document a standard, and then fix the bug. We are almost done with this project.)

Controller class
GeneralLibrary class
Mediator class
Model class
ModelEvent class
ModelListener interface
View class
ViewEvent class
ViewListener interface


package org.jsl.threedoor;

import java.util.Vector;

/**
* This class is the controller for the MVC Pattern.
*
* @author Jack Harich
*/
public class Controller implements ViewListener {

//---------- Internal Fields -------------------------------------
protected Vector models = new Vector();

//---------- ViewListener Implementation -------------------------
public void processViewEvent(ViewEvent evt) {
    print(".processViewEvent() - " + evt);
    
    // Given eventType, take appropriate action
    
    if (evt.getEventType() == ViewEvent.DOOR_SELECTED) {
        // NOTE - This could now be improved with getModeForID()
        for (int i = 0; i < models.size(); i++) {
            Model model = (Model)models.elementAt(i);
            if (evt.getID() == model.getID() ) {
                model.selectOneDoor(evt.getSelectedDoor());
                break; // Found correct model, why continue? :-)
            }    
        }
    } else if (evt.getEventType() == ViewEvent.SHOW_RESULTS) {
        
        // Determine win or loss
        boolean isWin = false;
        Model model = getModelForID(evt.getID());
        if (model.getSelectedDoor() == model.getCarDoor()) {
            isWin = true;
        }
        // Increment running score
        if (isWin) model.incrementScore(1);
        // Open all doors
        for (int i = 1; i <= View.NUMBER_DOORS; i++) {
            model.openDoor(i);
        }
    
    } else if (evt.getEventType() == ViewEvent.START_NEW_GAME) {
        // All this business logic is in the Model for now
        getModelForID(evt.getID()).startNewGame();
    
    } else if (evt.getEventType() == ViewEvent.EXIT) {
        System.exit(0);
    
    } else {
        print(".processViewEvent() - eventType = "
            + evt.getEventType() + " - Unknown event type!!!");
    }
}
//---------- Public Methods --------------------------------------
public void addModel(Model model) {
    models.addElement(model);
}
public void startNewGame() {
    for (int i = 0; i < models.size(); i++) {
        Model model = (Model)models.elementAt(i);
        model.startNewGame();
    }
}
//---------- Private Methods -------------------------------------
private Model getModelForID(int id) {
    for (int i = 0; i < models.size(); i++) {
        Model model = (Model)models.elementAt(i);
        if (model.getID() == id) return model;
    }
    return null; // Should never happen
}
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println("Controller" + text);
}

} // End class

package org.jsl.threedoor;

import java.util.Random;

/**
* These are static methods for general use.
* The main() is the unit test.
*
* @Author: Tim McCauley
*/
public final class GeneralLibrary {

//---------- Public Methods --------------------------------------
public static int getRandomInt(int lower, int upper) {
    Random random = new Random();

    int modValue = 0;
    int mod = 0;

    // if the same just return one of them
    if(lower == upper) return upper;

    // calculate the value for the modulus divisor
    // handle range like 0 to 3 and 2 to 5
    if((lower >= 0) && (upper > 0)) {
        mod = (upper - lower) + 1;
    } else if((lower < 0) && (upper >= 0)) {
        // handle range like -9 to 0 and -9 to 6
        mod = (Math.abs(lower) + upper) + 1;
    } else if((lower < 0) && (upper < 0)) {
        // handle range like -9 to -3
        mod = (Math.abs(lower) - Math.abs(upper)) + 1;
    }

    do {
        // +/- mod-1
        modValue = random.nextInt() % mod;
    } while( modValue < 0);

    return modValue + lower;
}
public static void main(String[] args) {
    int temp;
    int[] temparray = new int[7];
    for(int i=0; i<100000; i++) {
      temp = getRandomInt(3, 9);
      temparray[temp-3]++;
    }
    for(int i=0; i<temparray.length; i++) {
      System.out.println("value at " + (i+3) + " = " + temparray[i]);
    }
    for(int i=0; i<100000; i++) {
      temp = getRandomInt(0, 6);
      temparray[temp]++;
    }
    for(int i=0; i<temparray.length; i++) {
      System.out.println("value at " + i + " = " + temparray[i]);
    }
    for(int i=0; i<100000; i++) {
      temp = getRandomInt(-3, 3);
      temparray[temp+3]++;
    }    
    for(int i=0; i<temparray.length; i++) {
      System.out.println("value at " + (i-3) + " = " + temparray[i]);
    }
    for(int i=0; i<100000; i++) {
      temp = getRandomInt(-9, -3);
      temparray[temp+9]++;
    }
    for(int i=0; i<temparray.length; i++) {
      System.out.println("value at " + (i-9) + " = " + temparray[i]);
    }
    for(int i=0; i<100000; i++) {
      temp = getRandomInt(-6, 0);
      temparray[temp+6]++;
    }
    for(int i=0; i<temparray.length; i++) {
      System.out.println("value at " + (i-6) + " = " + temparray[i]);
    }
}
  
} // End class

package org.jsl.threedoor;

/**
* This class has the responsibilities of an "initializaer mediator".
* It creates the main objects, establishes their relationships, and
* lastly "starts" key objects. After that this class does nothing.
*
* @author Jack Harich
*/
public class Mediator {

//---------- Internal Fields -------------------------------------
protected static final int NUMBER_GAMES = 2;

//---------- Initialization --------------------------------------
public static void main(String[] args) {
    new Mediator().start();
}
//---------- Public Methods --------------------------------------
/**
* This starts the entire Three Door Game.
*/
public void start() {
    // Create main objects
    Controller controller = new Controller();
    
    for (int i = 1; i <= NUMBER_GAMES; i++) {
        // View
        View view = new View();
        view.setID(i);
        view.addViewListener(controller);
        view.setVisible(true);
        
        // Model
        Model model = new Model();
        model.setID(i);
        model.addModelListener(view);
        
        controller.addModel(model);
    }
    // Start the first game
    controller.startNewGame();
    
    print(".start() - started");
}
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println("Mediator" + text);
}

} // End class

	
  

package org.jsl.threedoor;

import java.util.Vector;

/**
* This class is the model in the MVC pattern. It emits ModelEvents
* which are sent to ModelListeners. It keeps all state for one
* game series.
*
* @author Jack Harich
*/
public class Model {

//---------- Internal Fields -------------------------------------
protected Vector    listeners = new Vector(); // ModelListeners
protected int       id;             // Matches the view with that model
protected int       runningScore;
protected int       carDoor;        // 1, 2, or 3
protected int       selectedDoor;   // 0 for none, 1, 2, or 3
protected boolean   isDone;         // true when Show Game Results clicked
protected boolean[] openedDoors = new boolean[ModelEvent.NUMBER_DOORS];

//---------- Properties ------------------------------------------
//----- id
public void setID(int id) {
    this.id = id;
}
public int getID() {
    return id;
}
//----- selectedDoor
public int getSelectedDoor() {
    return selectedDoor;
}
public void selectOneDoor(int selectedDoor) {
    this.selectedDoor = selectedDoor;
    fireModelEvent();
}
//----- Other
public int getCarDoor() {
    return carDoor;
}
//---------- Events ----------------------------------------------
public void addModelListener(ModelListener listener) {
    listeners.addElement(listener);
}
public void removeModelListener(ModelListener listener) {
    listeners.removeElement(listener);
}
//---------- Public Methods --------------------------------------
public void startNewGame() {

    //----- Reinitialize all game state
    selectedDoor = 0;
    isDone       = false;
    carDoor      = GeneralLibrary.getRandomInt(1, ModelEvent.NUMBER_DOORS);
    
    // Close all doors
    for (int i = 0; i < ModelEvent.NUMBER_DOORS; i++) {
        openedDoors[i] = false;
    }
    
    //----- Fire event
    fireModelEvent();
}
public void openDoor(int door) {
    openedDoors[door] = true;
    fireModelEvent();
}
public void incrementScore(int points) {
    runningScore += points;
    fireModelEvent();
}
//---------- Protected Methods -----------------------------------
protected void fireModelEvent() {

    // Configure the event
    ModelEvent event = new ModelEvent();
    event.setID(id);
    event.setSelectedDoor(selectedDoor);
    event.setCarDoor(carDoor);
    
    for (int i = 0; i < ModelEvent.NUMBER_DOORS; i++) {
        event.setOpenedDoor(i, openedDoors[i]);
    }
    // Send to listeners
    Vector list = (Vector)listeners.clone();
    // slower for (int i = 0; i < list.size(); i++) {
    for (int i = list.size() - 1; i >= 0; i--) {
        ModelListener listener = (ModelListener)list.elementAt(i);
        listener.processModelEvent(event);
    }
    print("ModelEvent fired to all listeners " + event);
}
//---------- Standard --------------------------------------------
private void print(String text) {
    System.out.println("Model " + id + " - " + text);
}

} // End class

package org.jsl.threedoor;

/**
* This class is a pure data structure, representing a single
* event fired by the Model. All indexed collections are zero based.
*
* @author Tim McCauley
*/
public class ModelEvent {

//---------- Public Fields ---------------------------------------
public static final int NUMBER_DOORS = 3;

//---------- Internal Fields -------------------------------------
protected int id;
protected int runningScore;
protected int carDoor;
protected int selectedDoor;
protected boolean[] openedDoors = new boolean[NUMBER_DOORS];

//---------- Superclass Override ---------------------------------
public String toString() {
    String text = "[id=" + id + ", runningScore=" + runningScore +
        ", carDoor=" + carDoor + ", selectedDoor=" + selectedDoor +
        ", openedDoors=";
    for (int i = 0; i < openedDoors.length; i++) {
        text += openedDoors[i];
        if (i < openedDoors.length - 1) text += ", ";
    }
    return text + "]";
}
//---------- Properties ------------------------------------------
//----- id
public void setID(int id) {
    this.id = id;
}
public int getID() {
    return id;
}
//----- running score
public void setRunningScore(int runningScore) {
    this.runningScore = runningScore;
}
public int getRunningScore() {
    return runningScore;
}
//----- carDoor
public void setCarDoor(int carDoor) {
    this.carDoor = carDoor;
}
public int getCarDoor() {
    return carDoor;
}
//----- selectedDoor
public void setSelectedDoor(int selectedDoor) {
    this.selectedDoor = selectedDoor;
}
/**
* Returns the selected door index or zero if none.
*/
public int getSelectedDoor() {
    return selectedDoor;
}
//---------- Public Methods --------------------------------------
public void setOpenedDoor(int door, boolean isOpen) {
    openedDoors[door] = isOpen;
} 
public boolean isDoorOpen(int door) {
    return openedDoors[door];
} 
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println("ModelEvent" + text);
}

} // End class
      

package org.jsl.threedoor;

/**
* This interface receives ModelEvents.
*
* @author Jack Harich
*/
public interface ModelListener {

//---------- Public Methods --------------------------------------
public void processModelEvent(ModelEvent evt);


} // End interface
  

package org.jsl.threedoor;

import java.awt.BorderLayout;
import java.awt.Button;
import java.awt.Color;
import java.awt.Label;
import java.awt.Panel;
import java.awt.Frame;
import java.awt.TextField;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Vector;

/**
* This class in the view in the MVC pattern. It receives
* ModelEvents and emits ViewEvents.
*
* @author Jack Harich, Tim McCauley, Cindy Nelson, Michel, etc
*/
public class View implements ModelListener, ActionListener {

//---------- Internal Fields -------------------------------------
protected Vector listeners = new Vector();   // list of ViewListeners
protected int    id;
protected int    selectedDoor; // Zero for none

// Widgets we need a reference to
protected Label[] selectedLabels = new Label[NUMBER_DOORS];
protected Label[] doorLabels     = new Label[NUMBER_DOORS];
protected TextField runningScoreTxt = new TextField();

protected Frame window = new Frame("Three Door Game");

protected static final int NUMBER_DOORS = 3;
protected static final String EXIT              = "Exit";
protected static final String SHOW_GAME_RESULTS = "ShowGameResults";
protected static final String START_NEW_GAME    = "StartNewGame";


//---------- Initialization --------------------------------------
public View() {
    // Init window
    window.setBackground(Color.lightGray);
    
    // Layout North section of window
    // Add the NUMBER_DOORS to the North panel
    Panel northPanel = new Panel();
    for (int i = 1; i <= NUMBER_DOORS; i++) {
        print(" the i = " + i);
        northPanel.add( addDoorPanel(i - 1) ); // Zero based
    }
    window.add(northPanel, "North");
    
    // Layout Center section
    Panel centerPanel = new Panel();
    centerPanel.add(createButton("Show Game Results", SHOW_GAME_RESULTS));
    centerPanel.add(new Label("Running Score"));
    centerPanel.add(runningScoreTxt);
    window.add(centerPanel, "Center");
    
    // Layout South section
    Panel southPanel = new Panel();
    southPanel.add(createButton("Start New Game", START_NEW_GAME));
    southPanel.add(createButton("Exit", EXIT));
    window.add(southPanel, "South");
    
    // Done
    window.pack();
}
/**
* This is the unit test.
*/
public static void main(String args[]) {
    View view = new View();
    view.setVisible(true);
    view.selectedLabels[2].setText("Selected");
    view.runningScoreTxt.setText("23");
}
//---------- ModelListener Implementation ------------------------
public void processModelEvent(ModelEvent evt) {
    // Check ID
    if (id != evt.getID()) {
        print(".processModelEvent() - the id's are not equal.");
    }
    // Show selected door
    // First erase all
    for (int i = 0; i < NUMBER_DOORS; i++) {
        selectedLabels[i].setText("");
    }   
    selectedDoor = 0;
    if (evt.getSelectedDoor() > 0) {
        selectedLabels[evt.getSelectedDoor()].setText("Selected");
        selectedDoor = evt.getSelectedDoor();
    }
    // Show opened doors, disable buttons for open doors
    for (int i = 0; i < NUMBER_DOORS; i++) {
        String text = "";
        if (evt.isDoorOpen(i)) {
            // Open
            text = (evt.getCarDoor() == i ? "CAR" : "Goat");
        }             
        doorLabels[i].setText(text);
    }
    // Update the running score
    runningScoreTxt.setText("" + evt.getRunningScore());
}
//---------- ActionListener Implementation -----------------------
public void actionPerformed(ActionEvent evt) {
    print(".processActionEvent() - " + evt.getActionCommand() +
    " clicked");
    
    // Create and configure the ViewEvent
    int eventType = 0;
    String actionCommand = evt.getActionCommand();
    
    if (actionCommand.equals(EXIT)) {
        eventType = ViewEvent.EXIT;
        
    } else if (actionCommand.equals(SHOW_GAME_RESULTS)) { 
        eventType = ViewEvent.SHOW_RESULTS;
        
    } else if (actionCommand.equals(START_NEW_GAME)) { 
        eventType = ViewEvent.START_NEW_GAME;
        
    } else {
        eventType = ViewEvent.DOOR_SELECTED;
    }
    ViewEvent event = new ViewEvent(eventType);
    event.setID(id);
    
    // Get door number clicked
    String stringNumber = evt.getActionCommand(); // 1 based
    int intNumber = 0;
    try {
        intNumber = Integer.parseInt(stringNumber);
    } catch(Exception ex) {
        print(".actionPerformed() - Door number is not int, it's " + stringNumber);
    }
    event.setSelectedDoor(intNumber);
    
    // Send event to all ViewListeners
    Vector list = (Vector)listeners.clone();
    for (int i = 0; i < list.size(); i++) {
        ViewListener listener = (ViewListener)list.elementAt(i);
        listener.processViewEvent(event);
    }
}
//---------- Properties ------------------------------------------
public void setID(int id) {
    this.id = id;
}
public int getID() {
    return id;
}
public void setVisible(boolean isVisible) {
    window.setVisible(isVisible);
}
//---------- Events ----------------------------------------------
public void addViewListener(ViewListener listener) {
    print(".addViewListener() - Listener added");
    listeners.addElement(listener);
}
public void removeViewListener(ViewListener listener) {
    listeners.removeElement(listener);
}
//---------- Protected Methods -----------------------------------
// Note the doorIndex is zero based
protected Panel addDoorPanel(int doorIndex) {
    Panel panel = new Panel();
    panel.setLayout(new BorderLayout());
    
    // Init arrays
    selectedLabels[doorIndex] = new Label();
    doorLabels[doorIndex]     = new Label("?");
    
    // Create and config button    
    Button button = createButton("Door " + (doorIndex + 1), 
        "" + (doorIndex + 1));
    
    // Add 3 widgets to panel
    panel.add(selectedLabels[doorIndex], "North");
    panel.add(doorLabels[doorIndex], "Center");
    panel.add(button, "South");
    
    return panel;
}
protected Button createButton(String text, String command) {
    Button button = new Button(text);
    button.setActionCommand(command);
    button.addActionListener(this);
    return button;
}
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println("View" + text);
}

} // End class

package org.jsl.threedoor;

/**
* This represents one symantic event in the View. It is a pure
* "data structure" class. This event is sent by
* the View to ViewListeners. All indexed collections are zero
* based.
*
* @author Tim McCauley
*/
public class ViewEvent {

//---------- Public Fields ---------------------------------------
// These are event type constants
public static final int EXIT           = 0;
public static final int START_NEW_GAME = 1;
public static final int SHOW_RESULTS   = 2;
public static final int DOOR_SELECTED  = 3;

//---------- Internal Fields -------------------------------------
protected int id;
protected int eventType;
protected int selectedDoor;

//---------- Initialization --------------------------------------
public ViewEvent(int eventType) {
    // TODO - Assert eventType is allowed
    this.eventType = eventType;
}  
//---------- Superclass Override ---------------------------------
public String toString() {
    String text = "[id=" + id + ", eventType=" + eventType +
        ", selectedDoor=" + selectedDoor;
    return text + "]";
}
//---------- Properties ------------------------------------------
//----- eventType
public int getEventType() {
    return eventType;
}
//----- id
public void setID(int id) {
    this.id = id;
}
public int getID() {
    return id;
}
//----- selectedDoor
public void setSelectedDoor(int selectedDoor) {
    this.selectedDoor = selectedDoor;
}
public int getSelectedDoor() {
    return selectedDoor;
}
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println("ViewEvent" + text);
}

} // End class

package org.jsl.threedoor;

/**
* This interface receives ViewEvents.
*
* @author Jack Harich
*/
public interface ViewListener {

//---------- Public Methods --------------------------------------
public void processViewEvent(ViewEvent evt);

} // End interface

	
  

Exploratory Code

At the 2/3/00 JSL meeting, after most left, several of us did a little code to explain how events work in Java. Here's the code. All are in the same package. This may be useful to those trying to mentally "click" on how patterns work. Good luck!!! :-)

/**
* This class is an event from the View in the MVC pattern.
*
* @author Jack Harich
*/
public class ViewEvent {

//---------- Public Fields ---------------------------------------
public static final int COMMAND = 1;

//---------- Internal Fields -------------------------------------
protected int    eventType;
protected String command;

//---------- Initialization --------------------------------------
public ViewEvent(int eventType) {
    this.eventType = eventType;
}
//---------- Properties ------------------------------------------
//----- eventType
public int getEventType() {
    return eventType;
}
//----- command
public void setCommand(String command) {
    this.command = command;
}
public String getCommand() {
    return command;
}
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println("ViewEvent" + text);
}

} // End class
==================================================================
/**
* This interface listens to ViewEvents.
*
* @author Jack Harich
*/
public interface ViewEventListener {

//---------- Public Methods --------------------------------------
public void processViewEvent(ViewEvent evt);

} // End interface
==================================================================
/**
* This class is the controller in the MVC pattern. It receives
* ViewEvents.
*
* @author Jack Harich
*/
public class Controller implements ViewEventListener {


//---------- ViewEventListener Implementation --------------------
public void processViewEvent(ViewEvent evt) {

    print(".processViewEvent() - Received command " +
        evt.getCommand());
}

//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println("Controller" + text);
}

} // End class
==================================================================
/**
* This class is the unit test for the Three Door system.
* <p>
* Run java org.jsl.threedoor.Test1
*
* @author Jack Harich
*/
public class Test1 {

//---------- Initialization --------------------------------------
public static void main(String arguments[]) {
    new Test1().runTest();
}
//---------- Public Methods --------------------------------------
public void runTest() {
    Controller controller = new Controller();
    
    // Create event
    ViewEvent evt = new ViewEvent(ViewEvent.COMMAND);
    evt.setCommand("Door3");
    
    // Simulate the View firing an event
    controller.processViewEvent(evt);
}
//---------- Standard --------------------------------------------
private static void print(String text) {
    System.out.println("Test1" + text);
}

} // End class