update:
Checkout MyMVC official website.
—-
I find software architecture is quite an interesting topic as I am learning it. A program is like an organization — how to assemble units such that the entire program is “good”? By “good” I mean it is easy to maintain and flexible to add more functions.
Maybe we can learn something from the organization of neural cells (or neurons) in our brain — after all, Nature is much better than us in organization. There are 3 facts about neurons and their communication:
- A neuron receives messages (through neurotransmitters), does some internal computation, then output some messages (through wires and neurotransmitters);
- When a neuron receives a message, it doesn’t care who sent the message and how the message was sent; When a neuron output a message, it doesn’t care who will receive it or how his message will be used;
- Neurons mostly communicate with neurons nearby; they make long distance communications very sparsely;
Here is a diagram of a neuron:
Translate to language in software engineering, we see
- A unit in a program receives events, do something, then dispatch events;
- A unit doesn’t care where the event comes or where the event will go;
- A unit should not communicate with another “long distance” unit.
Basically, each unit should be as local as possible (i.e., should not depend on other units).
What are units in a program? A component in View, a command, a manager are all units. So in the terminology of MVC (Model/View/Control),
- Controller interprets and handles all events, and call worker classes (commands).
- When a component in View dispatches an event, that event should not contain information from other components. For example, if a login button is clicked, this button can dispatch an event saying “i’m clicked”; the button should not dispatch an event saying “I’m clicked, and the username is xxx and password is yyy”. This is because the button should not depend on other components (such as username TextInput). It’s up to the root of View or the controller to interpret how to act with the “I’m clicked” event.
- Worker classes (commands) should dispatch events (progress, complete and error). Two worker classes can be concatenated to form a sequence worker class, assuming the output message of the first class and the input message of the second class are compatible.
(About event. As different events may use same name (event.type), also as broadcast/tune mode is used, it is sometimes confusing for a controller to tell who and what method dispatches the event. For example, two buttons of very different functions may both dispatch events of the same name ‘CLICKED’. When the controller hears ‘CLICKED’, it has to decide which one is clicked. Or when the controller hears ‘COMPLETE’ event, he has to decide who completes what. He can’t use event.target to determine the dispatcher as usually this event is dispatched by a common broadcast system (RadioStation). So when an event is dispatched, the event should have two more properties, who and what, for clarifications. This modification makes it very easy for multiple developers to write different components as they don’t have to worry if their event name is used.)
Cairngorm is a very nice framework and so I modified Cairngorm and add features I talked above. To avoid confusion, I rename the framework to mymvc.
Main change:
- Front controller can register a sequence of commands again an event
- Front controller can listen events and dispatch events
- Commands have to dispatch complete or error (or progress) event by notifyComplete, notifyError or notifyProgress
- Event has who and what properties, indicating the object and function which dispatch the event.
To illustrate the whole process, consider the following login and get friends list example. I first need clarify that there are two ways to dispatch events: (1) dispatchEvent(e:MyMVCEvent), a regular way where event dispatched by an object and bubble to its parents (chain mode); (2) MyMVCEvent.broadcast(), broadcast with a central radio station and anyone who access the radio station (especially front controller) can hear it (broadcast mode).
Now the example:
A user clicks ‘Log in and Get your friends’ button. This button dispatch ‘LOGIN_BUTTON_CLICKED’ event and bubbles it (chain mode, front controller can’t hear this event). The root of user interface receives this event and get the username and password, and broadcast this ”LOGIN_BUTTON_CLICKED” event with the login information (front controller can hear it).
Front controller then register a sequence command ([LoginCommand, DisplayFriendsCommand]) to handle this event. The LoginCommand creates a delegate to talk to the server and hands the results (login status and user’s friends list) to LoginCommand. Then LoginCommand sends out complete event together with the result (friends list). The DisplayFriendsCommand hears this complete event and update the data in ModelLocator. As the user interface is bound to ModelLocator, the friends list is displayed.
If there is error in the middle (either connection error or wrong password), LoginCommand will broadcast this error event which can be heard by front controller. Then front controller decides what to do (in this case, alert the user the error).
Here is a diagram of the flow:
Here is the code for controller. You can see from line 25 that it can register a sequence of commands.
// in controller package com.TestMyMVCService.control { import com.TestMyMVCService.commands.*; import com.TestMyMVCService.model.ModelLocator; import flash.utils.getQualifiedClassName; import flash.display.Stage; import mx.controls.Alert; import org.mymvc.*; public class Controller extends MyMVCController { public function Controller() { super(); initialize(); } private function initialize():void { // you can associate an event with a single command this.addCommand('LENGTHY_BUTTON_CLICKED', DoLengthyCommand); // or you can associate an event with a sequence command this.addCommands('LOGIN_BUTTON_CLICKED', [LoginCommand, DisplayFriendsCommand]); this.addEventListener(MyMVCEvent.ERROR, onError); this.addEventListener(MyMVCEvent.COMPLETE, onComplete); this.addEventListener(MyMVCEvent.PROGRESS, onProgress); } private function onError(e:MyMVCEvent):void { Alert.show(e.data.message); } private function onComplete(e:MyMVCEvent):void { var nameParts : Array; var className : String = ''; if(e.who is Array) { var ii:int; for(ii=0; ii<e.who.length; ii++) { nameParts = getQualifiedClassName( e.who[ii] ).split("::"); className = className + ',' + nameParts[ nameParts.length - 1 ]; } Alert.show("controller: I hear complete from sequence command: " + className); } else { nameParts = getQualifiedClassName( e.who ).split("::"); className = nameParts[ nameParts.length - 1 ]; Alert.show("controller: I hear complete from command: " + className); } } private function onProgress(e:MyMVCEvent):void { var root = ModelLocator.getInstance().root; root.progressPanel.progressbar.setProgress(e.data.progress,100); root.progressPanel.progressbar.label = e.data.progressMessage; } } }
Here is a source code (outdated, please refer to MyMVC official website)
MyMVC Library
The Example
And here are the file structure. The server-related files are simply Cairngorm files, the files in the red box are mymvc specific files.