上一篇讲了MVC-Model-1和MVP模式,并且简单实践了一下MVC模式,这一篇我们继续实践MVP模式。
前情提要
我们要做的就是这样一个简单的应用,下拉菜单可以选择歌手,选择之后下方列表可以显示歌手对应的专辑。
MVP模式相较于MVC,有2点明显的好处:对View层做了接口抽象,后期测试可以直接mock view
移除了View对Model层的依赖,提高代码可复用性
IView
对View层的抽象,明确了View层的输入和输出:
export interface IPresenterView {
selectedEvent: UIEvent;
bindRecords(data: Array): void;
bindAuthors(data: Array): void;
}
对上面的程序来说,输入就是下拉列表的数据和列表项的数据,输出就是下拉列表选中之后的触发事件。
Model
model层和MVC一样也比较简单,并且移除了Observable的特性,通过Repository模式,暴露几个接口:
export class RecordsRepos {
private static data: Array;
getRecords(): Array {
return RecordsRepos.data;
}
getRecordsByAuthorName(name: string): Array {
return RecordsRepos.data.filter(r => r.name === name);
}
}
export class AuthorsRepos {
private static data: Array;
getAuthors(): Array {
return AuthorsRepos.data;
}
}
Presenter
讲道理这里应该是讲View层实现了,但是我们既然已经有了IView的接口,Model也有了,就可以正常开发Presenter了。这样才可以把MVP的优点发挥出来。
export class RecordsPresenter {
recordsRepo: RecordsRepos;
authorRepo: AuthorsRepos;
view: IPresenterView;
constructor(view: IPresenterView) {
this.view = view;
this.view.bindAuthors(this.authorRepo.getAuthors());
this.view.bindRecords(this.recordsRepo.getRecords());
this.view.selectedEvent.on(this.onAuthorSelected);
}
onAuthorSelected(arg: SelectEventArg) {
let records = this.recordsRepo.getRecordsByAuthorName(arg.name);
this.view.bindRecords(records);
}
}
我们在构造函数中调用IView的2个方法手动绑定了初始数据,并且监听了View的下拉选择事件。在事件处理程序里,我们从Model获取了新的数据,并且重新绑定了专辑列表数据。
当然,上面有些写的不太对(所以上一篇才要郑重声明),比如没对2个Model实例化,并且Model也应该抽象出接口来,后期通过依赖注入,进一步提高可测试性。
View
最后就是我们的View层了,照着IView接口来实现就好了。
export class PresenterView implements IPresenterView {
selectedEvent: UIEvent;
dropdownList: IDropdownList;
list: IList;
constructor() {
this.dropdownList.selectedEvent.on(this.onAuthorSelected);
}
bindRecords(data: T[]): void {
this.list.data = data;
}
bindAuthors(data: T[]): void {
this.dropdownList.data = data;
}
onAuthorSelected(arg: SelectEventArg) {
this.selectedEvent.emit(arg);
}
}
一个简单的应用实践下来,我们其实也能发现MVP模式的一些弊端。
首先,就是一些重复性的代码太多,特别是更新View的操作。这就导致了Presenter有点重。
所以我们可以 拆分一部分简单的UI逻辑到View层,这就是MVP的一个分支,Supervising Controller。而我们上面实现的,是MVP的一个基本分支,Passive View。对于这两种模式有兴趣的同学可以自己再深入了解。
结尾再聊聊MVVM,最早(不一定准确)应该是由我软在2005年提出的,随着WPF和SilverLight被发扬光大(其实死的很早)。相比较于MVP,Presenter被换成了ViewModel。
从View层考虑,ViewModel其实就是View的一个抽象,包含了他所需的数据和行为。 从Model层来看,ViewModel又像是一个值转换器,将Model转换成View层需要的一种格式,所以有时候MVVM又被称为Model-View-Binder模式。
借助于Data-Binding技术,MVVM完成了Model和View的双向绑定,也不再包含对View层的依赖,让开发者可以更专注于和状态和行为的管理。
在我软技术栈上,Data-Binding主要由XACML来实现,而前端的主流框架,例如React,Vue则是借助于单向数据流+V-DOM diff实现类似效果的。
优点省去Presenter中Model和View之间的手动同步,移除了对View的依赖
代码更简洁,可读性更强
缺点在大型项目上MVVM的性能可能会有问题
最后借用 韦恩叔的一句话来总结一下:M-V- X 本质都是一样的 重点还是在于M-V 的桥梁要靠 X来牵线。
X的模式之间不同 主要是 M与V 的数据传递的流程不同。
数据传递的流程不同来源于运行环境技术栈能够做到的事情不同。