深入理解MVC
MVC的三个对象
MVC **(Model-View-Controller)**是一种软件设计模式,它把软件设计分成三大模块:模型(Model)、视图(View)、控制器(Controller)。这样重构的代码:
- 简化了后续对程序的修改和扩展简化,避免出现意大利面条式的代码
- 并使某一部分代码能够重复利用
- 同时,这一设计模式通过对复杂度的简化,使程序结构更加直观易于理解和维护。
- 模型(Model) - 负责存放和处理(增删改查)数据。一般与Conterller存在耦合,M从
服务器拿到数据后一般会通过eventBus传给Controller
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import $ from 'jquery';
const eventBus = $(window);
const m = { data:{}, create(){}, delete(){}, update(data){
Object.assign(m.data, data); eventBus.trigger('m已更新'); }, get(){} };
|
| const v = { el: null, html: `<!-- 要显示浏览器页面的内容 -->`, init(container){ v.el = $(container); }, render(data){ if(v.el.children) v.el.empty(); } };
|
- 控制器(Controller)- 负责监听和处理View事件,同时监听Model数据变化(通过eventBus)
| const c = { init(el){ v.init(el); v.render(data); c.autoBindEvents(); eventBus.on('m已更新',()=>{ v.render(data); }); }, events:{ }, method(){}, autoBindEvents(){} };
|
由于,v和c耦合度较高,一般将二者合为一个对象View。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import $ from 'jquery';
const eventBus = $(window);
class View extends eventBus{ constructor(options) { super();
Object.assign(this,options);
this.el = $(this.el); this.render(this.data); this.autoBindEvents(); this.on('m已更新', () => { this.render(this.data); }); }
autoBindEvents() {} }
export default View;
|
EventBus 实现对象间通信
EventBus就是一个以事件为驱动的消息服务总线,用来实现组件/对象之间的通信。
这样做的原理是:EventBus有三个关键的 API:on(监听事件),trigger(触发事件)或$emit(vue中),off(取消监听)
以上c和m之间的通信就是使用EventBus的一个例子。
实际工作中一般不用const eventBus = $(window);,因为这样写不利于调用和后期代码维护。一般单独封装一个EventBus类进行解耦。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import $ from "jquery";
class EventBus { constructor() { this._eventBus = $(window); }
on(eventName, fn) { return this._eventBus.on(eventName, fn); }
trigger(eventName, fn) { return this._eventBus.trigger(eventName, fn); }
off(eventName, fn) { return this._eventBus.off(eventName, fn); } }
export default EventBus;
|
表驱动编程(Table-Driven Approach)
首先看一段冗余代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| import $ from "jquery";
const testEl = $(window);
const v = { el: testEl, bindEvents() { v.cntnr.on('click', "#add1", () => { data += 1; });
v.cntnr.on('click', "#minus1", () => { data -= 1; });
v.cntnr.on('click', "#multiply2", () => { data <<= 1; }); v.cntnr.on('click', "#divide2", () => { data >>= 1; }); } };
v.bindEvents();
|
以上这种绑定事件的方法有如下缺点:
- 代码重复性:bindEvents方法明显存在大量重复代码,可以进一步提炼
- 可扩展性差:如果改变事件响应函数,就要改变bindEvents()方法,非常不方便
- 代码多的时候,不易读,主要程序逻辑被淹没在一些没用的冗余代码中
表驱动编程:
为解决上面这些问题,可以将代码发生变化的那一部分(一般是数据部分)放到表中,一般使用哈希表。而其他不变的部分(一般是逻辑部分),单独封装。这样后期维护只需要对表进行响应修改即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import $ from "jquery";
const testEl = $(window);
const v = { el:testEl, events:{ 'click #add1': 'add', 'click #minus1': 'minus', 'click #mul2': 'mul', 'click #divide2': 'divide', }, add() { data += 1; }, minus() { data -= 1; }, mul() { data <<= 1; }, divide() { data >>= 1; }, autoBindEvents(){ for(let key in v.events){ const value = v[v.events[key]]; const spaceIndex = key.indexOf(' '); const part1 = key.slice(0,spaceIndex); const part2 = key.slice(spaceIndex+1); v.el.on(part1,part2,value); } } }; v.autoBindEvents();
|
表驱动编程的意义在于实现了逻辑与数据的分离。
相应的,表驱动编程有以下优点:
- 提高代码可读性。代码简化到:只需要看“表”,就能知道程序是干什么的。
- 减少重复代码
- 增加代码可扩展性,比较方便。同时更易于控制复杂度
模块化
模块化是指解决一个复杂问题时自顶向下逐层把系统划分成若干模块的过程,有多种属性,分别反映其内部特性。模块化设计,简单地说就是程序的编写不是一开始就逐条录入计算机语句和指令,而是首先用主程序、子程序、子过程等框架把软件的主要结构和流程描述出来,并定义和调试好各个框架之间的输入、输出链接关系逐步求精的结果是得到一系列以功能块为单位的算法描述。以功能块为单位进行程序设计,实现其求解算法的方法称为模块化。
模块化的目的是为了降低程序复杂度,使程序设计、调试和维护等操作简单化。
以上封装m、v、c,解耦EventBus,以及表驱动编程的例子都体现了模块化的思想。
模块化具有以下优点:
控制了程序设计的复杂性。
提高了代码的重用性。
易于维护和功能扩充。
有利于团队开发。