关于mvc的第一篇–问题篇
游戏前端的代码一般可以分为以下几类:
- 保存服务器数据(也包括本地数据)的代码。
- 逻辑代码(数据处理逻辑和业务逻辑)。
- 数据处理逻辑。界面要根据数据显示,而这些数据一般都是要经过处理或者重新组织的。
- 业务逻辑。例如,某个按钮的响应,可能需要检测系统是否开启、是否有其他系统的限制,材料是否足够、金币是否足够,然后再调用服务器协议向服务器发送数据等。业务逻辑,一般是多个步骤的结合,也很可能涉及多个模块。
- 界面逻辑代码。
- 根据策划案和数据来展示界面。
- 接受玩家输入,并将输入转交控制器(controller)处理。
一、一般游戏项目的mvc职责。
mvc中的m指的是model,view指的是view,c指的是controller。mvc一般按模块划分,一个模块只有一个model,一个controller,和多个view。一般游戏项目的mvc的做法,会根据mvc的职责,将mvc的代码分开写。例如:
图1. 一般游戏项目的mvc职责
二、一般游戏项目mvc的几个问题和讨论
1、mvc的演变–mvc中的model和controller被合并了?
-
在图1中,对于model的职责,不少项目都没有第2点的职责,model只是单纯的保存服务器的数据,另外一些项目可能会提供一些数据处理逻辑的方法供外部调用。
-
当view显示的时候,需要数据,会有以下几种情况:a. 有些数据可能直接用model的数值就可以了;b. 而更多时候,数据要经过一定的处理才能满足策划案的需求;c. 数据也可能是需要来自其他模块的数据。这个时候数据的处理逻辑是应该写在model里面呢,还是应该写在controller里面呢?
-
对于以上的b和c,数据处理的代码,有些会放到model里面,有些会放到controller里面。而对于同一个项目,不同的人,可能理解不同,有些人会放到model,有些人会放到controller里面,甚至放到view里面进行处理。
-
大多数人的想法或做法趋向于,数据处理逻辑既可以放到controller里面,也可以放到model里面,而一般不会放在view里面。
-
这说明了一个问题,大多数项目,对于mvc来说,只有代码的分离的功能而已,对mvc各自的职责只是做了一个大致的区分。
-
基于以上理解上和做法上的混乱,有些项目后面就变成了model与controller合并了。也就是对于一个模块来说,以前的结构是一个model、一个controller和多个view,共三部分。而现在的结构变成一个model(或一个controller)和多个view,只保留了两部分了。笔者所经历或者换皮过的项目,就有两三个是后面这种结构的,究其原因,是某些主程认为mvc的作用只有代码的分离而已。如图:!
图2. mvc的某种演变
2、是否有界面管理器(view manager)
笔者所经历的项目,有些有界面管理器,有些则没有界面管理器。
- 有界面管理器的项目,根据功能将view抽象成一系列的派生类(基本view,多页签的view等)。界面的显示和关闭,由界面管理器控制。界面完全由界面管理器进行统一管理,每个界面都有一个对应的配置。
- 没有界面管理器的项目,界面的显示和关闭,由模块内的controller控制。每个界面的创建和销毁,由controller的一个方法控制。
笔者觉得第1种做法,是比较正确和简洁的做法。从依赖关系的角度上来说,有界面管理器的项目,只有view单向依赖controller,界面管理器对view只有弱依赖的关系(界面管理器,会持有view的引用,但并不依赖具体的view);而没有界面管理器的项目,view会依赖controller,而controller也会依赖view(controller负责具体view的创建和依赖)。
3、 不注重mvc之间依赖
model和controller一般被设计成单例,可以全局调用。如果不注意,这可能使得mvc之间的某些依赖关系的混乱,从而造成代码重用的困难。
例如,在view中随意的调用各个controller的方法。
class controller_a { //控制器a
/**单例模式,省略了实现 */
static get_instance() { }
ca_func_a() { }
}
class controller_b { //控制器b
/**单例模式,省略了实现 */
static get_instance() { }
cb_func_b() { }
}
class view_a { //界面a
v_func_a() {
// ...省略其他代码
controller_a.get_instance().ca_func_a();
}
v_func_b() {
//...省略其他代码
controller_b.get_instance().cb_func_b();
}
}
- 以上代码,你们是否看着眼熟呢?
- 在上面这段代码,view_a依赖controller_a和controller_b,而且这种依赖分散在类view_a的各个地方。那么请问,view_a的代码还能怎么重用呢?
- 假如现在有一个新的界面view_b,显示逻辑一样,但是不再依赖controller_a和controller_b,而是依赖controller_c和controller_d。如果view_b要重用view_a的代码,我们可以直接复制一份view_a的代码,类名改为view_b,然后再去改具体的代码。另外一个重用的方法,是view_b继承于view_a,然后只重写v_func_a和v_func_b的方法,这样能够重用一部分view_a的代码。
- 对于上面的依赖关系,有一种改进的方法,是将依赖集中在view_a类的一个函数中,例如下面的initDep方法中。
class view_a { //界面a
controller_a;
controller_b;
constructor(){
this.initDep();
}
initDep(){
/**将依赖集中在当前函数中 */
this.controller_a = controller_a.get_instance();
this.controller_b = controller_b.get_instance()
}
v_func_a() {
// ...省略其他代码
this.controller_a.ca_func_a();
}
v_func_b() {
//...省略其他代码
this.controller_b.cb_func_b();
}
}
那么对于前一种情况,view_b可以直接继承view_a,而controller_c和controller_d分别实现ca_func_a和cb_func_b方法。这样view_b的代码如下:
class view_b extends view_a {
initDep(){
/**将依赖集中在当前函数中 */
this.controller_a = controller_c.get_instance(); //controller_c实现了ca_func_a方法
this.controller_b = controller_d.get_instance(); //controller_d实现了cb_func_b方法
}
}
如此,能比较大限度的重用view_a的代码,这样做的修改比较少。但最好的方法,应该是加入依赖注入的实践,笔者在后面会提出一种做法。
- mvc按模块划分。
mvc一般按模块划分,一个模块只有一个model,一个controller,和多个view。这种做法,笔者认为也是不对的,很难达到代码复用的功能,mvc为什么不是按界面区分呢?笔者后面会提出一种按界面划分mvc的实践,能够更好的实现代码复用。
综上,大多数项目mvc,只是起到一个代码分离的功能,代码复用的功能很难发挥。在设计模式方面,它们只是加入了单例模式和观察者模式,但其实,我们还可以加入组合模式、策略模式、中介者模式,如果再加入依赖注入的思想,整个框架的复用性、健壮性等会得到很大的改善。
下一篇再见。
作者qq/wx:408293635
qq交流群:798568957
ps,作者正在寻找base广州的机会