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
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