框架模式之 MVC

常听说 spring MVC,现在都发展到前端 MVC 了


定义

经典 MVC 模式中,M 是指 Model 业务模型,V 是指 View 用户界面,C 则是 Controller 控制器

通常是为了使得代码模式固化,让代码复杂度相对稳定

表现

后端

比如在 spring 中,Model 通常包括 domain、mapper、dao 等三层,定义了对象模型以及对象对应的存取方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// domain
class User{
private Integer id;
User(){}
public Integer getId(){return this.id;}
public void setId(Integer id){this.id=id;}
}
// mapper
interface UserMapper{
User selectUserById(Integer id);
}
// dap
class UserDao{
@Autowired
private UserMapper userMapper;

public User selectUserById(Integer id){return userMapper.selectUserById(id);}
}

Controller 则是 controller 层 和 service 层,定义了数据接口和关于数据处理的业务方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// service
class UserService{
@Autowired
private UserDao userDao;

public User getUser(Integer id){return ResponseUtil.ok(userDao.selectUserById(id));}
}

// controller
@RestController("/user")
class UserController{
@Autowired
private UserService userService;

@GetMapping("/info")
public Object getUser(@RequestParam Integer userId){return userService.getUser(userId);}
}

特别的,当 controller 的注解是 @RestController 的时候,仅作为数据接口

当注解是 @Controller 的时候,可以返回数据或页面,比如 thymeleaf 渲染的页面

这时候,返回页面的 controller 就是所谓的 View

1
2
3
4
5
6
7
8
@Controller("")
class UserController{
@Autowired
private UserService userService;

@GetMapping("/index")
public String index(){return "index.html";}
}

前端

而在前端中,一般不需要考虑与数据库的交互,数据来源一般都是后端提供的数据接口

这时候,Controller 就不需要提供数据接口,只需要访问,于是原本在后端分离开的 controller 和 service 就融合在了一起,负责提供业务方法

1
2
3
4
5
6
7
8
9
10
11
const Controller = el => {
return {
el: el,
init(){
model.bindEvents(el)
},
add(target,value){
model.data[target]+=value
}
}
}

Model 则是负责保存需要用到的用户数据,并为数据设置事件

1
2
3
4
5
6
7
8
9
10
11
12
13
const Model = {
data: {
n: 100
},
events: {
click: ()=>console.log(n)
},
bindEvents(el){
for(let key in this.events){
el.addEventListener(key,this.events[key])
}
}
}

View 则是负责接收业务方法的信号,重新渲染页面,监听数据事件

1
2
3
4
5
6
7
const View = {
html: `233{{n}}`,
render(el){ // 被触发
html.replace('{{n}}',model.data.n)
el.innerHTML=this.html
}
}

于是又多了一个和后端不一样的地方——model 和 view 都要关注用户事件

所以前端 MVC 一般还有一个概念,就是 EventBus 事件总线

EventBus 提供一系列事件绑定、触发、解除等相关的方法,然后交由 Model 和 View 来调用

1
2
3
4
5
6
7
8
9
10
11
const EventBus = {
on(el, eventName, fn){
el.addEventListener(eventName,fn)
},
off(el, eventName, fn){
el.removeEventListener(eventName,fn)
},
trigger(el, eventName){
el.dispatchEvent(eventName)
}
}

但是现在这些方法怎么转移使用呢?

基于 js 的特性,可以选择原型链继承或者类继承

此处可以选择类继承,让 Model、View、EventBus 都成为 class

然后 Model、View 都 extends EventBus,就可以拥有 EventBus 的所有方法

之后在使用的时候,声明一个该类的实例即可

表驱动编程

上述伪代码中,我们采用了遍历 model.events 的方法来为 events 里的每个键值对都执行绑定

实际上就是先为要绑定的列表作成一个哈希表,然后遍历这个表

基于这种方法的编程,代码复杂度恒定——无论增加多少个表项,都不需要修改绑定部分的业务代码

模块化

其实 MVC 也就是一种模块化,毕竟代码分离了

模块化说到底就是为了方便维护各个部分的代码

比如同一个页面上有多块不同的功能,这时候我总不可以牵一发而动全身吧,那重构和渲染成本也太高了

这时候就可以选择在编程的时候模块化,使得代码耦合度降低,上层实现不直接依赖于底层架构

具体来说就是我们可以把页面上的每个不同功能的部分都应用一次 MVC 架构,这样每个部分都是直接依赖 MVC 提供的接口,而不关心内部的实现

就像前后端通信一样,只需要接口文档规定好,不需要调用者去关心接口内部是怎么写的

既然前后端分离的好处显而易见,那模块分离的好处,是不是也同理呢?


感谢阅读

--It's the end.Thanks for your read.--