这是我参与8月更文挑战的第5天,活动详情查看:8月更文挑战
程序的本质
程序的本质在于模拟现实,但是有更明确的分工
简单的一个例子: 我 写 代码。
这是一个主谓结构: 主语->我,谓语->写,宾语->代码。
现在让我们来面向视角看问题:
- 代码: 是个物体,是用来 被 写 的
- 写: 是个动作,是用来 被 我执行的
- 我: 是个物体,是用来 执行 写 这个动作 写代码的。
好,接着我们来面向对象写代码:
首先,创建一个我,这是个物体,所以应该创建一个对象:
public class Me {
}
然后,需要有代码,才能写,代码也是一个物体,那么再创建一个对象:
public class Code {
}
等等,代码应该有内容,有注释,好,我们来简单模拟下(程序就是模拟现实的):
public class Code {
// 代码
public String code;
// 注释
public String comment;
}
最后,需要创建一个写的动作,写既然是一个动作,不是物体,那么肯定是属于某个物体的行为,这里就是我的行为,动作就是函数(接口),于是就在 "我" 里面添加函数:
public class Me {
// 添加写的行为,写什么?写代码
public Code write() {
Code code = new Code();
code.code = "This is code";
return code;
}
}
这里有个问题,写过的代码,怎么展示出来呢,我们需要个显示器来显示,显示器是物体,所以我们需要定义个对象:
public class Display {
// 显示器可以显示内容
public void display(String content) {
System.out.println(content);
}
}
然后,我们需要展示我们的代码,我们可以直接这样改:
public class Me {
// 添加写的行为,写什么?写代码
public Code write() {
// 写代码
Code code = new Code();
code.code = "This is code";
// 展示
Display display = new Display();
display.display(code.code);
return code;
}
}
这样当然没问题,但是,write()明明是一个写的函数,却额外做了展示的事情,不满足SRP,万一我只想写,不想展示呢,所以我们将函数分离职责,如下:
public class Me {
// 添加写的行为,写什么?写代码
public Code write() {
// 写代码
Code code = new Code();
code.code = "This is code";
return code;
}
// 展示代码
public void showCode(){
// 写代码
Code code = write();
// 展示
Display display = new Display();
display.display(code.code);
}
}
这样也不对,因为showCode()里面又调用了写的动作,showCode()应该只负责展示代码的,怎么办呢?追起根源,展示代码这个动作,并不是我自己的行为,所以不应该放在Me里面,任何人都可以展示代码,比如,我把自己的代码提供给第三方,第三方只要拿着显示器,就能展示出来,所以,展示代码这个事情,应该是属于第三方的,好,现在我们把Me里面的showCode()删掉,创建一个第三方场景类:
public class Client {
// 展示代码
public void showCode(){
// 我来提供代码
Me me = new Me();
Code code = me.write();
// 让显示器来展示
Display display = new Display();
display.display(code.code);
}
}
上面我们绕了一大圈,最后也就这么一句话: 我在Client中写了代码,然后把它展示了出来。用程序的话来说就是 Client控制我写出Code,然后控制Display展示Code。
这里我们就引出了最基础的架构思想: MVC。MVC的核心就是一句话: C控制M展示在V上,这里Client就是C,Code就是M,Display就是V,所以是Client控制Code展示在Display上。至于Me,是负责提供生产Code的,就像是服务器是负责提供数据的一样。
MVC
MVC就是:Model,View,Controller的简写,核心是职责分离
我们知道,计算机由: 控制器,运算器,存储器,输入设备和输出设备组成,这里就是: 控制器 控制 存储器里面的内容 展示在 输出设备 上。所以MVC是个广义的思想,他不是架构,是思想,可以是物理的,也可以是虚拟的。
MVC的核心就是一句话: C控制M展示在V上,精粹就是四个字职责分离。
这里再强调一下,MVC是广义的概念,广义的就是思想,不是架构,或者从狭义来说,它也是一种架构。
我们来看下MVC的结构图:
这里我们可以看到,MVC本身的耦合是挺严重的,M和V竟然也有关联,这确实不应该的。但是MVC的核心是职责分离而不是解耦合,体现在设计上就是,MVC的核心是单一职责,而不是最少知识。
在普通的Android应用中,M就是数据,V就是xml布局,C明显就是Activity。这里有点不太对劲儿,Activity明明更像一个View,因为它有findViewById()的方法,为什么又是C呢,这岂不是违背了MVC的职责分离明确的原则吗?这只能说谷歌设计的不太好。于是就有了下面的MVP模式。
MVP
MVP就是:Model,View,Presenter,这里把Controller替换为了Presenter。
MVP的核心除了MVC的职责分离,还有解耦合,也就是说,他在满足单一职责的基础上,又满足了最少知识原则。我们看下它的结构图:
这里我们看到,View和Model没有关联了,它们都通过Presenter来沟通,是不是有点像中介者模式。中介者模式的优点不就是解耦合吗,正好!
现在假如我们使用了MVP模式,我们的代码看起来是这样:
class Model {
Presenter presenter;
}
class View {
Presenter presenter;
}
class Presenter {
Model model;
View view;
}
可以看到,View和Model是零耦合的。
那么事件的流向就是: View -> Presenter -> Model
数据的流向就是: Model -> Presenter -> View
中间都需要经过Presenter。
当我们在Android中使用时,可以把Activity当作Presenter,把xml当作View,当然也可以直接把Activity抽空,自定义一个Presenter。这里我们来个简单的例子示例一下MVP中事件和数据的流向。
假设现在屏幕上有一个按钮,点击之后需要展示数据,事件肯定是屏幕引起的,也就是View,所以事件的出发点是View。
public class View {
Presenter presenter;
// 1 发出事件
public void click(){
presenter.getInfo();
}
// 6 接收并展示数据
public void showInfo(String info){
setInto(info);
}
}
public class Presenter {
Model model;
View view;
// 2 传递事件
public void getInfo(){
model.getInfo();
}
// 5 传递数据
public void onGetInfo(String info){
view.showInfo(info);
}
}
public class Model {
Presenter presenter;
// 3 接收并处理事件
public void getInfo(){
String info = getInfoFromServer();
// 4 发出数据
presenter.onGetInfo(info);
}
// 模拟从服务器获取数据
private String getInfoFromServer(){
String info = "this is info";
return info;
}
}
事件的流向(1->2->3): View.click() -> presenter.getInfo() -> model.getInfo() -> 从服务器获取数据。
数据的流向(4->5->6): model.getInfo() -> presenter.onGetInfo(info) -> view.showInfo(info) -> 展示在屏幕上。
可以看到,View是事件的发起者和数据的接收者,Model是事件的接收者和数据的发起者,Presenter只是起个中转作用。
好,现在我们知道了MVP除了具有MVC的职责分离优点,还能解耦合。接下来我们来看自动化的MVP-MVVM
MVVM
MVVM就是: Model,View,ViewModel。
MVVM的核心是观察者模式,MVVM已经不再职责分离了,当然也没解耦合,他的特点就是响应式。什么意思呢,就是说: 我这边数据变了,你那边立刻知道,不需要经过谁来通知。
官方的图是这样的,View和Modle无关联,但是这是不严谨的,因为对于MVVM来说,Model数据变了后,需要通知到View,那么肯定需要直接或间接持有View的引用,所以这个图是不严谨的。
我们将上述MVP的代码改写为MVVM。
class View {
ViewModel vm;
// 订阅Model的数据
private void init(){
vm.getModel.observer(content, new Observer() {
public void onInfo(String info) {
// 6 接收到数据并展示
showInfo(info);
}
})
}
// 1 发起事件
private void click(){
vm.getInfo();
}
// 7 展示数据
private void showInfo(String info){
setInfo(info);
}
}
class ViewModel {
Model model;
Model getModel(){
return model;
}
// 2 传递事件
public void getInfo(){
model.getIndo();
}
}
class Model {
// 定义观察者,这里已经持有观察者了,也就是持有View了。
List<Observer> observers;
// 添加观察者
public void observe(Observer observe){
observers.add(observe);
}
// 3 接收并处理事件
public void getInfo(){
// 4 获取数据
String info = getInfoFromServer();
// 5 通知数据改动
for(Observer observer : observers) {
observer.onInfo(info);
}
}
// 模拟从服务器获取数据
private String getInfoFromServer(){
String info = "this is info";
return info;
}
}
经过上面伪代码,我们发现ViewModel只传递了事件,不再传递数据了,数据是直接由Model通知到View的,所以我们的:
事件流向: View.click() -> ViewModel.getInfo() -> Model.getInfo() -> 获取数据。
数据流向: Model.getInfo() -> View.Observer.onInfo(info) -> View.showInfo() -> 展示数据。
这里有个很egg pain的点,就是Model间接持有View,第一就是会导致耦合,第二就是可能发生内存泄漏,我们知道Model的生命周期是大于View的,所以要在View消失的时候去反注册掉。当然,使用LiveData可以更好的解决问题。
那么,MVVM的优点在哪呢?就是你不用去主动去刷新UI了,只要Model数据变了,会自动反映到UI上。换句话说,MVVM更像是自动化的MVP。
总结
- MVC更像是一种思想,它描述一种职责分离的思想
- MVP是MVC的一种表现,它出了具备职责分离,还具备解耦合
- MVVM是自动化的MVVM,它具备职责分离,具备松耦合,同时还能自动响应数据。
如果你的业务是少量的重逻辑,建议使用MVP(debug方便);如果你的业务是大量的轻逻辑,最好使用MVVM(自动响应数据方便)。