MVC模式现在已经铺天盖地了,不论是写程序多年的老家伙还是刚会写hello world之类的newbie,估计都听说过mvc的大名;在下也是经历多年摸索,自觉才算摸得一点门道;个人见解整理如下,如有错误偏颇还请指正;
1. 引子,谈模式
谈MVC首先应当清楚什么叫模式,模式英文叫Pattern,全称设计模式(Design
Pattern),这东西就是那些挂了的和还没挂的先人们发明和验证了特别适合解决某一种或者某一个问题的开发经验和相关的代码组成的总结,鲁迅先生说过
这世界上本没有pattern,用的人多了,就成了pattern;现在pattern里面最知名的一个怕就是mvc了,它最早是用来解决相同的数据需要
不同的表示方式这样的问题的,比如说我有数据data={name:LaoHuang, gender:
Male},我要把它以html格式显示出来那么结果可以是
<ul>
<li><b>Name:b>{data.name}li>
<li><b>Gender:b>{data.gender}li>
ul>
如果这数据以xml格式来显示呢,那么可以是
1 <name>{data.name}name>
2 <gender>{data.gender}gender>
显而易见这2种方式的区别仅仅在于数据最终显示的样子不同,这就是mvc的初衷了,用M存数据,然后每一种显示格式就是一个V,至于嘛时候用哪个V,就是C的活了;根据外面输入来把M的数据套进用户想要的V里面送给用户,这就是一个比较完美的mvc发挥效力的实例了;
虽然mvc初衷是解决这个问题,但是数据只有一种表示方式,是不是就不合适用这个
模式呢,答案否定的,因为采用了mvc后,还有个额外的好处,就是数据处理的逻辑,可以跟数据显示的逻辑完全隔离开来,所以现在大多数使用的地方,都是为
了做到逻辑的分离来用的,最典型的就是web开发领域了,程序员写M层和C层,把显示层就是图片啊html/css啊的丢给美工去做,这样美工可以不懂程
序逻辑,程序员可以不懂美工的那套,能够做到术业有专攻;
2. 一步一步迈入MVC
看了到这里是否感觉mvc很简单呢,enen是的,mvc本身就那么点东西,不过在实际使用中,我们往往比较容易犯的错误是一段代码,放m里面能成,放c
里面能成,甚至放v里面也成,由此我们再强调一遍,m层仅用来表示数据,v层仅负责显示,那些流程啊,逻辑啊丢给c去做;您可能说了,丫啰嗦那么久都是哥
们姐们早知道的,没一点自己见解,好啊好下面在下用一个实际的小例子来说明下titanium mobile里面,怎么实际的去应用它;
2.1 代码初级阶段
现在我们考虑一个很常见的足够简单&&能说明问题的例子,登陆界面,然后显示个Welcome
{username};这个东西很简单的,谁都写的出来,于是下面的代码产生了(在下提个建议,既然您已经看到这里了,不妨自己写写试试,看看自己写出来
啥风格).
//app.js
var loginWindow = CreateLoginWindow();
loginWindow.open();
function CreateLoginWindow(){
var win = Ti.UI.createWindow(...);
var view = Ti.UI.createView(...);
var usernameInput = Ti.UI.createTextFileld(...);
var passwordInput = Ti.UI.createTextFiled(...);
var loginButton = Ti.UI.createButton(...);
loginButton.addEventListener('click',function(e){
if('username' == usernameInput.value && 'password' == passwordInput.value){
win.close();
var win2 = CreateWelcomeWindow(username);
win2.open();
}else{
alert("Wrong username or password");
}
});
view.add(usernameInput);
view.add(passwordInput);
view.add(loginButton);
win.add(view);
return win;
}
function CreateWelcomeWindow(username){
var win = Ti.UI.createWindow(...);
var view = Ti.UI.createView(...);
var welcomeLabel = Ti.UI.createLabel(...);
welcomeLabel.text = "Welcome " + username;
view.add(welcomeLabel);
win.add(view);
return win;
}
2.2 以文件来组织功能
各位看官看完上面这段代码有何感想呢,简单清晰,能完成功能,也够模块化;我们就问题一个一个来说明下并讨论解决方案;
首先来说把所有的代码都堆在一个文件里面是不好的做法,我们应当把实现不同功能的东西,分在不同的文件里面;
比如上面的function CreateLoginWindow,可以写进LoginWindow.js; CreateWelcomeWindow写进WelcomeWindow.js;
于是现在我们的app.js变成
1 //app.js
2 Ti.include('login.js');
3 Ti.include('welcome.js');
4
5 var loginWindow = CreateLoginWindow();
6 loginWindow.open();
38行的一个文件,变成了6行的;简单的多了,不错不错;
2.3 初探MVC之创建M
那么我们再去看下LoginWindow.js;虽然看上去它是个创建显示层的东西,但是它里面包含了一些非显示层的逻辑(第一段代码中的第13行),好现在我们把它抽出来,就可以做出来一个伟大的M层了,大致代码如下;
//model/user.js
function User(username,password){
this.username = username;
this.password = password;
}
User.prototype.login=function(){
if('username' == this.username && 'password' == this.password){
return 1;
}else{
return 0;
}
}
现在我们已经把M层抽出来,LoginWindow.js就能简化成如下:
1 //LoginWindow.js
2 Ti.indlude('model/user.js');
3
4 function CreateLoginWindow(){
5 var win = Ti.UI.createWindow(...);
6 var view = Ti.UI.createView(...);
7 var usernameInput = Ti.UI.createTextFileld(...);
8 var passwordInput = Ti.UI.createTextFiled(...);
9 var loginButton = Ti.UI.createButton(...);
10
11 loginButton.addEventListener('click',function(e){
12 var user = new User(usernameInput.value,passwordInput.value);
13 if(user.login()){
14 win.close();
15 var win2 = CreateWelcomeWindow(username);
16 win2.open();
17 }else{
18 alert("Wrong username or password");
19 }
20 });
21
22 view.add(usernameInput);
23 view.add(passwordInput);
24 view.add(loginButton);
25 win.add(view);
26 return win;
27 }
现在看上去;我们有了M层(user.js);
2.4 初探MVC之V和C
就再分个View层出来吧,于是我们很happy的把LoginWindow.js和WelcomeWindow.js改名成
login.js/welcome.js,并把它们都放进view目录;于是一个比较完美的mvc结构就出来了;等等,等等,您老这明明只说了M和V
吗,C哪去了?哈哈,回头看看我们现在总共有4个文件,login.js/welcome.js/user.js/app.js前面3个已经有名花有主
了,这不是app.js正好就是C吗;那么我们现在mvc家族齐全,完美落座;
2.5 MVC概念回顾
那现在我们再回去套套mvc的概念,user.js是m层,负责数据,并封装了自己的login方法供调用,app.js是C,调用V层来打开
window;welcome.js是个纯粹的V,用来显示欢迎信息,呵呵;这不是mvc都做好了吗;以在下观点来看,大多数人对mvc的理解也就到此为
止了,都是仅仅做到了一半,而mvc最后的那层纸还没有捅破,各位看官请仔细看login.js,再对照前文所说"V层应当仅仅负责显示",而目前的
login.js实际上还包含了2个本不应当属于V的东西,就是做实际的登陆验证,和打开之后的欢迎窗口,这属于流程控制,纯粹的mvc做法应当归结到C
里面去;所谓行百里者半九十,而怎么处理这最后的一点才是最关键的;好我们现在开始动手,不是说login.js里面那块不应当在么,那就转移掉好了,就
是把上段代码的12~19行去掉;同时第二行那个Ti.include('model/user.js')也不需要了;把这段逻辑拷贝到app.js;于
是我们的app.js变成
//app.js
Ti.include('view/login.js');
Ti.include('view/welcome.js');
Ti.include('model/user.js');
var loginWindow = CreateLoginWindow();
loginWindow.open();
var user = new User(???, ???);
if(user.login()){
loginWindow.close();
var welcomeWindow = CreateWelcomeWindow();
welcomeWindow.open();
}else{
alert('Wrong username or password');
}
看上去很简单吧;呵呵,这层纸很容易捅么,一点就破,还被你说的好像多神秘;不过
请仔细看代码种第10行,这里怎么直接打开loginWindow就执行了,应当是loginWindow里面点了登陆的按钮才执行的,而且如我们已
知,new
User时,需要2个参数,一个是username,一个是password,以我们目前的做法,这2个值应当从哪里来呢,"应当从
loginWindow来啊,多直观",但是但是,怎么来呢?
这就牵扯V跟C的数据交互问题了;我们这就是需要V把用户输入的输入提交给C;可是V里面怎么传给C呢,此处省略1万字,请带着这2个问题,闭目思考怎么
来解决它;
2.6 彻底迈入MVC
只要我们有了这个思路,解决还是很容易的,我们知道ti里面可以添加自定义的eventlistener的,此处我们用一个eventlistener就能比较优雅的解决这个问题;好现在看login.js里面怎么做:
1 //view/login.js
2
3 function CreateLoginWindow(){
4 var win = Ti.UI.createWindow(...);
5 var view = Ti.UI.createView(...);
6 var usernameInput = Ti.UI.createTextFileld(...);
7 var passwordInput = Ti.UI.createTextFiled(...);
8 var loginButton = Ti.UI.createButton(...);
9
10 loginButton.addEventListener('click',function(e){
11 win.fireEvent('win:login',{
12 username:usernameInput.value,
13 password:passwordInput.value
14 });
15 });
16
17 view.add(usernameInput);
18 view.add(passwordInput);
19 view.add(loginButton);
20 win.add(view);
21 return win;
22 }
然后再看修改后的app.js
//app.js
Ti.include('view/login.js');
Ti.include('view/welcome.js');
Ti.include('model/user.js');
var loginWindow = CreateLoginWindow();
loginWindow.addEventListener('win:login',function(data){
var user = new User(data.username,data.password);
if(user.login()){
loginWindow.close();loginWindow = null;//dont forget to null it;
var welcomeWindow = CreateWelcomeWindow();
welcomeWindow.open();
}else{
alert('Wrong username or password');
}
});
loginWindow.open();
上面说的是使用EventListener的一种实现方式;实用时我们还可以采用callback方式;就是在前面login.js代码中11行那把
win.fireEvent('win:login',data); 替换为 callback(data);而同样CreateLoginWindow需要传递个callback函数进去;
至于是eventlistener方式还是callback方式孰好孰坏,在下认为没有本质区别都是可以的,不过还是偏好eventlistener方式,感觉更自然些;
3.总结
现在我们重新整理一下我们的最后结果,我们生成了以下的目录结构
1 Resources/
2 /app.js
3 /model/user.js
4 /view/login.js
5 /view/welcome.js
再回顾一下我们各个文件的作用,app.js初始化页面,并给login.js创建实际的eventlistern;做后面的页面切换;
对于像这个简单的例子,我们直接以app.js充当C即可,如果应用复杂了,我们可能需要创建更多的C,并建立专门的cotnroller目录;
而login.js显示登陆内容后,点用户点击了登陆按钮后,触发自己定义的win:login事件后就告结束;
下一步的welcome.js则仅仅显示内容就完事;这个例子虽然简单,但是足以说明问题;而且真正做到了MVC各司其职;
另外,本次的例子里面完全是简单js实现,没有使用commonjs风格;后续将采用commonjs重写本次代码的例子;