MVC(轉載)

overview

study model/view/controller design pattern ("mvc")

see how to apply mvc in flash

build an example mvc application: a clock

mvc: basic structure

mvc separates the code required to manage a user interface into three distinct classes:

model: stores the data and application logic for the interface view: renders the interface (usually to the screen) controller: responds to user input by modifying the model

benefits of mvc

allows multiple representations (views) of the same information (model)

allows user interfaces (views) to be easily added, removed, or changed

allows response to user input (controller) to be easily changed

changes can happen dynamically at runtime

promotes code reuse (e.g., one view might be used with different models)

allows multiple developers to simultaneously update the interface, logic, or input of an application without affecting other source code

helps developers focus on a single aspect of the application at a time

communication between the mvc classes

model, view, and controller communicate regularly

for example:

model notifies the view of state changes view registers controller to receive user interface events (e.g., "onClick()" controller updates the model when input is received

object references in mvc

each object in mvc stores a reference to the objects it communicates with

model stores a reference to the view instances view stores a reference to the model view stores a reference to the controller controller stores a reference to the model controller stores a reference to the view

mvc communication cycle

typical mvc communication cycle starts with user input:

view receives user input and passes it to the controller controller receives user input from the view controller modifies the model in response to user input model changes based on an update from the controller model notifies the view of the change view updates the user interface (i.e., presents the data in some way, perhaps by redrawing a visual component or by playing a sound)

in some cases, the controller modifies the view directly and does not update the model at all

for example, alphabetization of a ComboBox affects the view only, not the underlying data

hence, in the case of alphabetization, controller modifies the view directly

model responsibilities

store data in properties

implement application methods (e.g., ClockModel.setTime() or ClockModel.stop())

provide methods to register/unregister views

notify views of state changes

implement application logic (e.g., the clock ticking)

view responsibilities

create interface

update interface when model changes

forward input to controller

controller responsibilities

translate user input into changes in the model

if change is purely cosmetic, update view

mvc framework

create a reusable mvc framework to implement the mvc pattern

participants:

mvc.View: an interface all views must implement mvc.Controller: an interface all controllers must implement mvc.AbstractView: a generic implementation of the View interface mvc.AbstractController: a generic implementation of the Controller interface util.Observable: superclass of the model (model extends Observable) util.Observer: an interface all views must implement

for details on Observable and Observer, see this lecture

View interface implementation

the View interface specifies the methods every view must provide:

methods to set and retrieve the controller reference

public function setController (c:Controller):Void; public function getController ():Controller;

methods to set and retrieve the model reference

public function setModel (m:Observable):Void; public function getModel ():Observable;

a method that returns the default controller for the view

public function defaultController (model:Observable):Controller;

View interface source:

import util.*; import mvc.*; /** * Specifies the minimum services that the "view" * of a Model/View/Controller triad must provide. */ interface mvc.View { public function setModel (m:Observable):Void; public function getModel ():Observable; public function setController (c:Controller):Void; public function getController ():Controller; public function defaultController (model:Observable):Controller; }

AbstractView class implementation

AbstractView is a convenience class that implements the methods defined by View and Observer

in an mvc application, the views extend AbstractView

AbstractView class skeleton and constructor

AbstractView implements both the Observer and View interfaces:

class mvc.AbstractView implements Observer, View { }

AbstractView properties and constructor

AbstractView's properties store a reference to the model and the controller:

private var model:Observable; private var controller:Controller;

AbstractView is passed its model reference via its constructor:

public function AbstractView (m:Observable, c:Controller) { setModel(m); if (c !== undefined) { setController(c); } }

if a controller is passed to the constructor, it becomes the view's controller

if no controller is passed, one is created by defaultController() the first time getController() is invoked

AbstractView's defaultController() method

defaultController() returns the default controller for the view (which is null until otherwise assigned):

public function defaultController (model:Observable):Controller { return null; }

subclasses of AbstractView override defaultController() to specify a functional default controller

Retrieving and assigning the AbstractView's model

AbstractView defines accessor methods to get/set the model reference:

public function setModel (m:Observable):Void { model = m; } public function getModel ():Observable { return model; }

Retrieving and assigning the AbstractView's controller

setController() assigns this view its controller

notice that when a controller is assigned, it is passed a reference back to the view

public function setController (c:Controller):Void { controller = c; // Tell the controller this object is its view. getController().setView(this); }

getController() returns the view's controller

if a controller has not yet been assigned, getController() creates one

public function getController ():Controller { // If a controller hasn't been defined yet... if (controller === undefined) { // ...make one. setController(defaultController(getModel())); } return controller; }

AbstractView lets subclasses draw the interface

every view must render the interface when the model invokes update()

each AbstractView subclass defines its own interface-creation code, so AbstractView leaves update() empty

public function update(o:Observable, infoObj:Object):Void { }

AbstractView source code

here's the complete source listing for AbstractView

import util.*; import mvc.*; /** * Provides basic services for the "view" of * a Model/View/Controller triad. */ class mvc.AbstractView implements Observer, View { private var model:Observable; private var controller:Controller; public function AbstractView (m:Observable, c:Controller) { // Set the model. setModel(m); // If a controller was supplied, use it. Otherwise let the first // call to getController() create the default controller. if (c !== undefined) { setController(c); } } /** * Returns the default controller for this view. */ public function defaultController (model:Observable):Controller { return null; } /** * Sets the model this view is observing. */ public function setModel (m:Observable):Void { model = m; } /** * Returns the model this view is observing. */ public function getModel ():Observable { return model; } /** * Sets the controller for this view. */ public function setController (c:Controller):Void { controller = c; // Tell the controller this object is its view. getController().setView(this); } /** * Returns this view's controller. */ public function getController ():Controller { // If a controller hasn't been defined yet... if (controller === undefined) { // ...make one. Note that defaultController() is normally overridden // by the AbstractView subclass so that it returns the appropriate // controller for the view. setController(defaultController(getModel())); } return controller; } /** * A do-nothing implementation of the Observer interface's * update() method. Subclasses of AbstractView will provide * a concrete implementation for this method. */ public function update(o:Observable, infoObj:Object):Void { } }

Controller interface implementation

the Controller interface specifies the methods every controller must provide:

methods to set and retrieve the view reference

public function setView (v:View):Void; public function getView ():View;

methods to set and retrieve the model reference

public function setModel (m:Observable):Void; public function getModel ():Observable;

Controller interface source:

import util.*; import mvc.*; /** * Specifies the minimum services that the "controller" of * a Model/View/Controller triad must provide. */ interface mvc.Controller { /** * Sets the model for this controller. */ public function setModel (m:Observable):Void; /** * Returns the model for this controller. */ public function getModel ():Observable; /** * Sets the view this controller is servicing. */ public function setView (v:View):Void; /** * Returns this controller's view. */ public function getView ():View; }

AbstractController class implementation

AbstractController is a convenience class that implements the methods defined by the Controller interface

in an mvc application, the controllers extend AbstractController

AbstractController stores a reference to the model and the view:

private var model:Observable; private var view:View;

AbstractController is passed its model reference via its constructor:

public function AbstractController (m:Observable) { // Set the model. setModel(m); }

AbstractController defines accessor methods to get and set the model:

public function setModel (m:Observable):Void { model = m; } public function getModel ():Observable { return model; }

AbstractController defines accessor methods to get and set the view:

public function setView (v:View):Void { view = v; } public function getView ():View { return view; }

AbstractController source code

here's the source code for the AbstractController class

import util.*; import mvc.*; /** * Provides basic services for the "controller" of * a Model/View/Controller triad. */ class mvc.AbstractController implements Controller { private var model:Observable; private var view:View; /** * Constructor * * @param m The model this controller's view is observing. */ public function AbstractController (m:Observable) { // Set the model. setModel(m); } /** * Sets the model for this controller. */ public function setModel (m:Observable):Void { model = m; } /** * Returns the model for this controller. */ public function getModel ():Observable { return model; } /** * Sets the view that this controller is servicing. */ public function setView (v:View):Void { view = v; } /** * Returns this controller's view. */ public function getView ():View { return view; } }

an mvc clock

now let's build an mvc clock based on our mvc framework

the classes in the clock are:

Clock the main application class, which creates the MVC clock ClockModel the model class, which tracks the clock's time ClockUpdate an info object class that stores update data sent by ClockModel to all views ClockAnalogView a view class that presents the analog clock display ClockDigitalView a view class that presents the digital clock display ClockTools a view class that presents the Start, Stop, and Reset buttons ClockController a controller class that handles button input for ClockTools

the ClockModel class

ClockModel is the model, so it extends Observable

class mvcclock.ClockModel extends Observable { }

ClockModel properties describe the clock's state:

hour current hour, from 0 (midnight) to 23 (11 p.m.) minute current minute, from 0 to 59 second current second, from 0 to 59 isRunning Boolean indicating whether the clock’s internal ticker is running or not

ClockModel's public methods allow the clock state to be set:

setTime() sets the time, then notifies views if appropriate start() starts the clock's internal ticker, then notifies the views stop() stops the clock’s internal ticker, then notifies the views

private methods used to validate data and update the time:

isValidHour() checks whether a number is a valid hour (i.e., an integer from 0 to 23) isValidMinute() checks whether a number is a valid minute (i.e., is an integer from 0 to 59) isValidSecond() checks whether a number is a valid second (i.e., is an integer from 0 to 59) tick() increments the second property by 1

methods of Observable (superclass) handle view registration/notification

addObserver() removeObserver() notifyObservers()

ClockModel source code

here's the complete source listing for ClockModel

import util.Observable; import mvcclock.*; /** * Represents the data of a clock (i.e., the Model of the MVC triad). */ class mvcclock.ClockModel extends Observable { // The current hour. private var hour:Number; // The current minute. private var minute:Number; // The current second. private var second:Number; // The interval identifier for the interval that calls "tick()" once per second. private var tickInterval:Number; // Indicates whether the clock is running or not. private var isRunning:Boolean; /** * Constructor. */ public function ClockModel () { // By default, set the clock time to the current system time. var now:Date = new Date(); setTime(now.getHours(), now.getMinutes(), now.getSeconds()); } /** * Starts the clock ticking. */ public function start ():Void { if (!isRunning) { isRunning = true; tickInterval = setInterval(this, "tick", 1000); var infoObj:ClockUpdate = new ClockUpdate(hour, minute, second, isRunning); setChanged(); notifyObservers(infoObj); } } /** * Stops the clock ticking. */ public function stop ():Void { if (isRunning) { isRunning = false; clearInterval(tickInterval); var infoObj:ClockUpdate = new ClockUpdate(hour, minute, second, isRunning); setChanged(); notifyObservers(infoObj); } } /** * Sets the current time (i.e., the hour, minute, and second variables. * Notifies observers of any change in time. * * @param h The new hour. * @param m The new minute. * @param s The new second. */ public function setTime (h:Number, m:Number, s:Number):Void { if (h != null && h != hour && isValidHour(h)) { hour = h; setChanged(); } if (m != null && m != minute && isValidMinute(m)) { minute = m; setChanged(); } if (s != null && s != second && isValidSecond(s)) { second = s; setChanged(); } // If the model has changed, notify Views. if (hasChanged()) { var infoObj:ClockUpdate = new ClockUpdate(hour, minute, second, isRunning); // Push the changed data to the Views. notifyObservers(infoObj); } } /** * Checks to see if a number is a valid hour (i.e., is * an integer in the range 0 to 23.) * * @param h The hour to check. */ private function isValidHour (h:Number):Boolean { return (Math.floor(h) == h && h >= 0 && h <= 23); } /** * Checks to see if a number is a valid minute (i.e., is * an integer in the range 0 to 59.) * * @param m The minute to check. */ private function isValidMinute (m:Number):Boolean { return (Math.floor(m) == m && m >= 0 && m <= 59); } /** * Checks to see if a number is a valid second (i.e., is * an integer in the range 0 to 59.) * * @param s The second to check. */ private function isValidSecond (s:Number):Boolean { return (Math.floor(s) == s && s >= 0 && s <= 59); } /** * Makes time pass by adding a second to the current time. */ private function tick ():Void { // Get the current time. var h:Number = hour; var m:Number = minute; var s:Number = second; // Increment the current second, adjusting // the minute and hour if necessary. s += 1; if (s > 59) { s = 0; m += 1; if (m > 59) { m = 0; h += 1; if (h > 23) { h = 0; } } } // Set the new time. setTime(h, m, s); } }

the ClockUpdate class

ClockUpdate info object sent by the ClockModel to its views when an update occurs

ClockUpdate's properties indicate the time and whether or not the clock is running

source listing:

class mvcclock.ClockUpdate { public var hour:Number; public var minute:Number; public var second:Number; public var isRunning:Boolean; public function ClockUpdate (h:Number, m:Number, s:Number, r:Boolean) { hour = h; minute = m; second = s; isRunning = r; } }

the ClockAnalogView class

creates a circular clock

display-only view, so no controller (no input)

ClockAnalogView is a subclass of AbstractView

class mvcclock.ClockAnalogView extends AbstractView { }

performs two tasks when ClockAnalogView instance is created:

pass model, controller references to superclass create the clock visual

public function ClockAnalogView (m:Observable, c:Controller, target:MovieClip, depth:Number, x:Number, y:Number) { super(m, c); makeClock(target, depth, x, y); }

the makeClock() method creates the clock graphics

public function makeClock (target:MovieClip, depth:Number, x:Number, y:Number):Void { clock_mc = target.attachMovie("ClockAnalogViewSymbol", "analogClock_mc", depth); clock_mc._x = x; clock_mc._y = y; }

the update() method handles updates from the model

update() makes the clock visually match the state of the model

public function update (o:Observable, infoObj:Object):Void { // Cast the generic infoObj to the ClockUpdate datatype. var info:ClockUpdate = ClockUpdate(infoObj); // Display the new time. var dayPercent:Number = (info.hour > 12 ? info.hour - 12 : info.hour) / 12; var hourPercent:Number = info.minute/60; var minutePercent:Number = info.second/60; clock_mc.hourHand_mc._rotation = 360 * dayPercent + hourPercent * (360 / 12); clock_mc.minuteHand_mc._rotation = 360 * hourPercent; clock_mc.secondHand_mc._rotation = 360 * minutePercent; // Fade the display out if the clock isn't running. if (info.isRunning) { clock_mc._alpha = 100; } else { clock_mc._alpha = 50; } }

ClockAnalogView source code

here's the complete source listing for ClockAnalogView

import util.*; import mvcclock.*; import mvc.*; /** * An analog clock view for the ClockModel. This View has no user * inputs, so no Controller is required. */ class mvcclock.ClockAnalogView extends AbstractView { // Contains an instance of the ClockAnalogViewSymbol, which // depicts the clock on screen. private var clock_mc:MovieClip; /** * Constructor */ public function ClockAnalogView (m:Observable, c:Controller, target:MovieClip, depth:Number, x:Number, y:Number) { // Invoke superconstructor, which sets up MVC relationships. // This view has no user inputs, so no controller is required. super(m, c); // Create UI. makeClock(target, depth, x, y); } /** * Creates the movie clip instance that will display the * time in analog format. * * @param target The clip in which to create the movie clip. * @param depth The depth at which to create the movie clip. * @param x The movie clip's horizontal position in target. * @param y The movie clip's vertical position in target. */ public function makeClock (target:MovieClip, depth:Number, x:Number, y:Number):Void { clock_mc = target.attachMovie("ClockAnalogViewSymbol", "analogClock_mc", depth); clock_mc._x = x; clock_mc._y = y; } /** * Updates the state of the on-screen analog clock. * Invoked automatically by ClockModel. * * @param o The ClockModel object that is broadcasting an update. * @param infoObj A ClockUpdate instance describing the changes that * have occurred in the ClockModel. */ public function update (o:Observable, infoObj:Object):Void { // Cast the generic infoObj to the ClockUpdate datatype. var info:ClockUpdate = ClockUpdate(infoObj); // Display the new time. var dayPercent:Number = (info.hour > 12 ? info.hour - 12 : info.hour) / 12; var hourPercent:Number = info.minute/60; var minutePercent:Number = info.second/60; clock_mc.hourHand_mc._rotation = 360 * dayPercent + hourPercent * (360 / 12); clock_mc.minuteHand_mc._rotation = 360 * hourPercent; clock_mc.secondHand_mc._rotation = 360 * minutePercent; // Fade the display out if the clock isn't running. if (info.isRunning) { clock_mc._alpha = 100; } else { clock_mc._alpha = 50; } } }

the ClockDigitalView class

creates a text-based clock

the ClockDigitalView class is structurally identical to ClockAnalogView

ClockDigitalView differs in the following ways:

makeClock() creates a text field instead of attaching a movie clip:

public function makeClock (target:MovieClip, depth:Number, x:Number, y:Number):Void { // Make the text field. target.createTextField("clock_txt", depth, x, y, 0, 0); // Store a reference to the text field. clock_txt = target.clock_txt; // Assign text field characteristics. clock_txt.autoSize = "left"; clock_txt.border = true; clock_txt.background = true; }

update() changes the numeric text of the clock instead of the position of the hands

public function update (o:Observable, infoObj:Object):Void { // Cast the generic infoObj to the ClockUpdate datatype. var info:ClockUpdate = ClockUpdate(infoObj); // Create a string representing the time in the appropriate format. var timeString:String = (hourFormat == 12) ? formatTime12(info.hour, info.minute, info.second) : formatTime24(info.hour, info.minute, info.second); // Display the new time in the clock text field. clock_txt.text = timeString; // Fade the color of the display if the clock isn't running. if (info.isRunning) { clock_txt.textColor = 0x000000; } else { clock_txt.textColor = 0x666666; } }

ClockDigitalView adds methods to format the time numerically in either 12-hour or 24-hour format

private function formatTime24 (h:Number, m:Number, s:Number):String { var timeString:String = ""; // Format hours... if (h < 10) { timeString += "0"; } timeString += h + separator; // Format minutes... if (m < 10) { timeString += "0"; } timeString += m + separator; // Format seconds... if (s < 10) { timeString += "0"; } timeString += String(s); return timeString; } /** * Returns a formatted 12-hour time string. * * @param h The hour. * @param m The minute. * @param s The second. */ private function formatTime12 (h:Number, m:Number, s:Number):String { var timeString:String = ""; // Format hours... if (h == 0) { timeString += "12" + separator; } else if (h > 12) { timeString += (h - 12) + separator; } else { timeString += h + separator; } // Format minutes... if (m < 10) { timeString += "0"; } timeString += m + separator; // Format seconds... if (s < 10) { timeString += "0"; } timeString += String(s); return timeString; }

ClockDigitalView source code

here's the complete source listing for ClockDigitalView

import util.*; import mvcclock.*; import mvc.*; /** * A digital clock View for the ClockModel. This View has no user * inputs, so no Controller is required. */ class mvcclock.ClockDigitalView extends AbstractView { // The hour format. private var hourFormat:Number = 24; // The separator character in the clock display. private var separator:String = ":"; // The text field in which to display the clock. private var clock_txt:TextField; /** * Constructor */ public function ClockDigitalView (m:Observable, c:Controller, hf:Number, sep:String, target:MovieClip, depth:Number, x:Number, y:Number) { // Invoke superconstructor, which sets up MVC relationships. super(m, c); // Make sure the hour format specified is legal. If it is, use it. if (hf == 12) { hourFormat = 12; } // If a separator was provided, use it. if (sep != undefined) { separator = sep; } // Create UI. makeClock(target, depth, x, y); } /** * Creates the onscreen text field that will display the * time in digital format. * * @param target The clip in which to create the text field. * @param depth The depth at which to create the text field. * @param x The text field's horizontal position in target. * @param y The text field's vertical position in target. */ public function makeClock (target:MovieClip, depth:Number, x:Number, y:Number):Void { // Make the text field. target.createTextField("clock_txt", depth, x, y, 0, 0); // Store a reference to the text field. clock_txt = target.clock_txt; // Assign text field characteristics. clock_txt.autoSize = "left"; clock_txt.border = true; clock_txt.background = true; } /** * Updates the state of the on-screen digital clock. * Invoked automatically by ClockModel. * * @param o The ClockModel object that is broadcasting an update. * @param infoObj A ClockUpdate instance describing the changes that * have occurred in the ClockModel. */ public function update (o:Observable, infoObj:Object):Void { // Cast the generic infoObj to the ClockUpdate datatype. var info:ClockUpdate = ClockUpdate(infoObj); // Create a string representing the time in the appropriate format. var timeString:String = (hourFormat == 12) ? formatTime12(info.hour, info.minute, info.second) : formatTime24(info.hour, info.minute, info.second); // Display the new time in the clock text field. clock_txt.text = timeString; // Fade the color of the display if the clock isn't running. if (info.isRunning) { clock_txt.textColor = 0x000000; } else { clock_txt.textColor = 0x666666; } } /** * Returns a formatted 24-hour time string. * * @param h The hour. * @param m The minute. * @param s The second. */ private function formatTime24 (h:Number, m:Number, s:Number):String { var timeString:String = ""; // Format hours... if (h < 10) { timeString += "0"; } timeString += h + separator; // Format minutes... if (m < 10) { timeString += "0"; } timeString += m + separator; // Format seconds... if (s < 10) { timeString += "0"; } timeString += String(s); return timeString; } /** * Returns a formatted 12-hour time string. * * @param h The hour. * @param m The minute. * @param s The second. */ private function formatTime12 (h:Number, m:Number, s:Number):String { var timeString:String = ""; // Format hours... if (h == 0) { timeString += "12" + separator; } else if (h > 12) { timeString += (h - 12) + separator; } else { timeString += h + separator; } // Format minutes... if (m < 10) { timeString += "0"; } timeString += m + separator; // Format seconds... if (s < 10) { timeString += "0"; } timeString += String(s); return timeString; } }

the ClockTools class

like ClockAnalogView and ClockDigitalView, ClockTools is a view for ClockModel

hence, ClockTools is a subclass of AbstractView

ClockTools creates buttons to control the clock

unlike ClockAnalogView and ClockDigitalView, ClockTools accepts input

hence, ClockTools has a controller

the controller handles input events for the buttons created by ClockTools

controller class is ClockController

general structure of ClockTools follows ClockAnalogView and ClockDigitalView:

makeTools() method creates the user interface update() method changes the interface based on ClockModel updates

but makeTools() doesn't just render the user interface

makeTools() also registers the controller to handle events from the interface

ClockTools: setting the controller

to set its controller, the ClockTools class overrides AbstractView.defaultController():

public function defaultController (model:Observable):Controller { return new ClockController(model); }

recall the code for AbstractView.getController():

public function getController ():Controller { if (controller === undefined) { setController(defaultController(getModel())); } return controller; }

if no controller has been created when ClockTools.getController() is called, ClockTools' version of defaultController() runs

the ClockTools.defaultController() returns a ClockController instance

from then on, ClockTools.getController() returns the ClockController instance

ClockTools: making the buttons

ClockTools.makeTools() creates three buttons

buttons are placed in a container movie clip:

var tools_mc:MovieClip = target.createEmptyMovieClip("tools", depth);

buttons are components created via createClassObject()

startBtn = tools_mc.createClassObject(Button, "start", 0);

the instance name, "start", is used by ClockController to identify the button when it is clicked

next, the button label is set:

startBtn.label = "Start";

then, the button is either disabled or enabled:

startBtn.enabled = false;

start button is disabled because the clock is already running

code for all three buttons:

startBtn = tools_mc.createClassObject(Button, "start", 0); startBtn.label = "Start"; startBtn.enabled = false; stopBtn = tools_mc.createClassObject(Button, "stop", 1); stopBtn.label = "Stop"; stopBtn.enabled = false; stopBtn.move(startBtn.width + 5, startBtn.y); resetBtn = tools_mc.createClassObject(Button, "reset", 2); resetBtn.label = "Reset"; resetBtn.move(stopBtn.x + stopBtn.width + 5, startBtn.y);

connecting buttons to ClockController

when each button component is created, ClockController is registered as its event handler:

startBtn.addEventListener("click", getController()); stopBtn.addEventListener("click", getController()); resetBtn.addEventListener("click", getController());

notice that ClockController class is not specified directly!

instead, the object registered to handle event is whatever getController() returns

this allows the controller to be changed easily, even at runtime

updating the ClockTools view

like ClockAnalogView and ClockDigitalView, ClockTools changes its interface when ClockModel invokes update()

if the clock is currently running, update() disables the start button and enables the stop button but if the clock is not currently running, update() disables the stop button and enables the start button

code listing for ClockTools.update()

public function update (o:Observable, infoObj:Object):Void { // Cast the generic infoObj to the ClockUpdate datatype. var info:ClockUpdate = ClockUpdate(infoObj); // Enable the start button if the clock is stopped, or // the stop button if the clock is running. if (info.isRunning) { stopBtn.enabled = true; startBtn.enabled = false; } else { stopBtn.enabled = false; startBtn.enabled = true; } }

ClockTools source code

here's the complete source listing for ClockTools

import util.*; import mvcclock.*; import mvc.*; import mx.controls.Button; /** * Creates a user interface that can control a ClockModel. */ class mvcclock.ClockTools extends AbstractView { private var startBtn:Button; private var stopBtn:Button; private var resetBtn:Button; /** * Constructor */ public function ClockTools (m:Observable, c:Controller, target:MovieClip, depth:Number, x:Number, y:Number) { // Invoke superconstructor, which sets up MVC relationships. super(m, c); // Create UI. makeTools(target, depth, x, y); } /** * Returns the default controller for this view. */ public function defaultController (model:Observable):Controller { return new ClockController(model); } /** * Creates a movie clip instance to hold the clock start, stop, * and reset buttons, and also creates those buttons. * * @param target The clip in which to create the tools clip. * @param depth The depth at which to create the tools clip. * @param x The tools clip's horizontal position in target. * @param y The tools clip's vertical position in target. */ public function makeTools (target:MovieClip, depth:Number, x:Number, y:Number):Void { // Create a container movie clip. var tools_mc:MovieClip = target.createEmptyMovieClip("tools", depth); tools_mc._x = x; tools_mc._y = y; // Create UI buttons in the container clip. startBtn = tools_mc.createClassObject(Button, "start", 0); startBtn.label = "Start"; startBtn.enabled = false; startBtn.addEventListener("click", getController()); stopBtn = tools_mc.createClassObject(Button, "stop", 1); stopBtn.label = "Stop"; stopBtn.enabled = false; stopBtn.move(startBtn.width + 5, startBtn.y); stopBtn.addEventListener("click", getController()); resetBtn = tools_mc.createClassObject(Button, "reset", 2); resetBtn.label = "Reset"; resetBtn.move(stopBtn.x + stopBtn.width + 5, startBtn.y); resetBtn.addEventListener("click", getController()); } /** * Updates the state of the user interface. * Invoked automatically by ClockModel. * * @param o The ClockModel object that is broadcasting an update. * @param infoObj A ClockUpdate instance describing the changes that * have occurred in the ClockModel. */ public function update (o:Observable, infoObj:Object):Void { // Cast the generic infoObj to the ClockUpdate datatype. var info:ClockUpdate = ClockUpdate(infoObj); // Enable the start button if the clock is stopped, or // the stop button if the clock is running. if (info.isRunning) { stopBtn.enabled = true; startBtn.enabled = false; } else { stopBtn.enabled = false; startBtn.enabled = true; } } }

the ClockController class

ClockController class has the following responsibilities:

handle input for ClockTools change the state of ClockModel in response to input

here are the state-changing methods:

startClock() stopClock() resetClock() setTime() setHour() setMinute() setSecond()

ClockController defines a click() method that is invoked whenever a button is clicked

click() method determines which button was clicked, then takes appropriate action:

public function click (e:Object):Void { switch (e.target._name) { case "start": startClock(); break; case "stop": stopClock(); break; case "reset": resetClock(); break; } }

the action taken is always a model method invocation

for example, the startClock() method invokes ClockModel.startClock():

public function startClock ():Void { ClockModel(getModel()).start(); }

ClockController source code

here's the complete source listing for ClockController

import mvcclock.ClockModel; import mvc.*; import util.*; /** * Makes changes to the ClockModel's data based on user input. * Provides general services that any view might find handy. */ class mvcclock.ClockController extends AbstractController { /** * Constructor * * @param cm The model to modify. */ public function ClockController (cm:Observable) { super(cm); } /** * Starts the clock ticking. */ public function startClock ():Void { ClockModel(getModel()).start(); } /** * Stops the clock ticking. */ public function stopClock ():Void { ClockModel(getModel()).stop(); } /** * Resets the clock's time to 12 midnight. */ public function resetClock ():Void { setTime(0, 0, 0); } /** * Changes the clock's time. * * @param h The new hour. * @param m The new minute. * @param s The new second. */ public function setTime (h:Number, m:Number, s:Number):Void { ClockModel(getModel()).setTime(h, m, s); } // As these next three methods show, the controller can provide // convenience methods to change data in the model. /** * Sets just the clock's hour. * * @param h The new hour. */ public function setHour (h:Number):Void { ClockModel(getModel()).setTime(h, null, null); } /** * Sets just the clock's minute. * * @param m The new minute. */ public function setMinute (m:Number):Void { ClockModel(getModel()).setTime(null, m, null); } /** * Sets just the clock's second. * * @param s The new second. */ public function setSecond (s:Number):Void { ClockModel(getModel()).setTime(null, null, s); } /** * Handles events from the start, stop, and reset buttons * of the ClockTools view. */ public function click (e:Object):Void { switch (e.target._name) { case "start": startClock(); break; case "stop": stopClock(); break; case "reset": resetClock(); break; } } }

communication cycle example

here's an example sequence of events in the clock:

User clicks on the Reset button ClockController.click() receives the event ClockModel.setTime() invoked by ClockController, with zeros for the hour, minute, and second ClockModel changes the time ClockModel.notifyObservers() broadcasts an update (containing a ClockUpdate info object) to all registered views ClockAnalogView, ClockDigitalView, and ClockTools receive the update event ClockAnalogView resets both hands to 12 o'clock ClockDigitalView resets the digital display to 00:00:00. ClockTools disables and enables the appropriate buttons

putting it all together

assembly of the mvc clock happens in Clock class

Clock class performs these tasks:

creates the ClockModel instance

clock_model = new ClockModel();

creates the clock views (ClockAnalogView, ClockDigitalView, ClockTools)

clock_digitalview = new ClockDigitalView(clock_model, undefined, 24, ":", target, 0, 253, 265); clock_analogview = new ClockAnalogView(clock_model, undefined, target, 1, 275, 200); clock_tools = new ClockTools(clock_model, undefined, target, 2, 120, 300);

registers views with the ClockModel

clock_model.addObserver(clock_digitalview); clock_model.addObserver(clock_analogview); clock_model.addObserver(clock_tools);

optionally sets the clock's time

clock_model.setTime(h, m, s);

starts the clock ticking

clock_model.start();

Clock source code

here's the complete source listing for Clock

import mvcclock.* /** * An example model-view-controller (MVC) clock application. */ class mvcclock.Clock { // The clock data (i.e., the Model). private var clock_model:ClockModel; // Two different displays of the clock's data (i.e., the Views). private var clock_analogview:ClockAnalogView; private var clock_digitalview:ClockDigitalView; // A toolbar for controlling the clock. private var clock_tools:ClockTools; /** * Clock Constructor * * @param target The movie clip to which the digital and * analog views will be attached. * @param h The initial hour at which to set the clock. * @param m The initial minute at which to set the clock. * @param s The initial second at which to set the clock. */ public function Clock (target:MovieClip, h:Number, m:Number, s:Number) { // Create the data model. clock_model = new ClockModel(); // Create the digital clock view. clock_digitalview = new ClockDigitalView(clock_model, undefined, 24, ":", target, 0, 253, 265); clock_model.addObserver(clock_digitalview); // Create the analog clock view. clock_analogview = new ClockAnalogView(clock_model, undefined, target, 1, 275, 200); clock_model.addObserver(clock_analogview); // Create the clock tools view. clock_tools = new ClockTools(clock_model, undefined, target, 2, 120, 300); clock_model.addObserver(clock_tools); // Set the time. clock_model.setTime(h, m, s); // Start the clock ticking. clock_model.start(); } /** * System entry point. Starts the clock application running. */ public static function main (target:MovieClip, h:Number, m:Number, s:Number) { var clock:Clock = new Clock(target, h, m, s); } }

summary

mvc makes an application easy to extend and change

for example, the following extensions could all be added without any changes to the existing clock code

a view that can display a different time zone a view that makes a ticking sound every second and a gong every hour a view that lets the user set the current time

mvc requires work to implement, but for complex applications, the works saves time in the long run

remember that full mvc is overkill for smaller applications

but the general principles of dividing logic, presentation, and input-handing apply to all situations

from: http://moock.org/lectures/mvc/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章