站在思想层面看MVX架构

8/19/2021 架构MVX

# 程序的本质

程序的本质在于模拟现实,但是有更明确的分工

简单的一个例子: 我 写 代码。

这是一个主谓结构: 主语->我,谓语->写,宾语->代码。

现在让我们来面向视角看问题:

  • 代码: 是个物体,是用来 被 写 的
  • 写: 是个动作,是用来 被 我执行的
  • 我: 是个物体,是用来 执行 写 这个动作 写代码的。

好,接着我们来面向对象写代码:

首先,创建一个我,这是个物体,所以应该创建一个对象:

public class Me {

}
1
2
3

然后,需要有代码,才能写,代码也是一个物体,那么再创建一个对象:

public class Code {

}
1
2
3

等等,代码应该有内容,有注释,好,我们来简单模拟下(程序就是模拟现实的):

public class Code {
    // 代码
    public String code;
    // 注释
    public String comment;
}
1
2
3
4
5
6

最后,需要创建一个写的动作,写既然是一个动作,不是物体,那么肯定是属于某个物体的行为,这里就是我的行为,动作就是函数(接口),于是就在 "我" 里面添加函数:

public class Me {
    // 添加写的行为,写什么?写代码
    public Code write() {
        Code code = new Code();
        code.code = "This is code";
        return code;
    }
}
1
2
3
4
5
6
7
8

这里有个问题,写过的代码,怎么展示出来呢,我们需要个显示器来显示,显示器是物体,所以我们需要定义个对象:

public class Display {
    // 显示器可以显示内容
    public void display(String content) {
        System.out.println(content);
    }
}
1
2
3
4
5
6

然后,我们需要展示我们的代码,我们可以直接这样改:

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;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

这样当然没问题,但是,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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

这样也不对,因为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);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

上面我们绕了一大圈,最后也就这么一句话: 我在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

这里我们可以看到,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的职责分离,还有解耦合,也就是说,他在满足单一职责的基础上,又满足了最少知识原则。我们看下它的结构图:

MVP

这里我们看到,View和Model没有关联了,它们都通过Presenter来沟通,是不是有点像中介者模式。中介者模式的优点不就是解耦合吗,正好!

中介者模式

现在假如我们使用了MVP模式,我们的代码看起来是这样:

class Model {
    Presenter presenter;
}

class View {
    Presenter presenter;
}

class Presenter {
    Model model;
    View view;
}
1
2
3
4
5
6
7
8
9
10
11
12

可以看到,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
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
40
41
42
43
44
45

事件的流向(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已经不再职责分离了,当然也没解耦合,他的特点就是响应式。什么意思呢,就是说: 我这边数据变了,你那边立刻知道,不需要经过谁来通知。

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;
    }
}
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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64

经过上面伪代码,我们发现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(自动响应数据方便)。

Last Updated: 1/28/2022, 2:19:00 PM