MVC与MVP在项目实践中的总结

2018-01-17  本文已影响70人  April_Android

嗯 最近比较休闲了,先泡一杯碧螺春定定神,然后开始总结MVC与MVP的项目运用中的优缺点,刚开始用MVP这个架构开始项目搭建的时候可谓是小心翼翼呀,也是激动万分的;使用之后觉得也就那么回事儿,面向对象的编程万变不离其宗,只有掌握了扎实的编程基础,一切都好说;

首先简单阐述一下MVC;

1.png

MVC分为:Model(数据抽象)、View(视图)、Controller(控制器)的三层架构。接下来我们分别来一一解析每一层所对应的职责分别是什么。

View层:对应的则是Android中的layout文件夹中的xml文件,在启动Activity/Fragment的时候,都会加载一个R.layout.xxx的布局文件,使得在视图中显示出我们在xml中定义好的视图。

Controller层:对应的则是Activity/Fragment。当Activity/Fragment加载了layout文件后,我们需要在Activity/Fragment中findViewById(int)去寻找到相对应的view,并对找到的view设置相应的属性以及监听器。而在设置view的属性之前,我们很有可能会先到model中请求一次数据,当数据回调回来后controller就会去更新view了。

Model层:对应的则是一些DataSource以及DataBean的相关对象,这里的DataSource指的是数据的来源。一般数据的来源有2个主要的地方,一个是sqlite,一个是webservice,而我们习惯于将这两种数据的来源封装在一个repository中,对于调用者而言只需要调用repository中的一个获取接口来获取数据,但是这个数据是从内存中还是sqlite还是webservice来,我们都不得而知,从保护了调用实现的逻辑,分解相关的实现,达到调用者的极度简单与简洁,且在单元测试中测试接口也是非常方便的。

首先是View:Activity_view.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">

<Button
    android:id="@+id/btn_hello_mvc"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_gravity="center"
    android:text="Hello MVC" />

</FrameLayout>
接下来是Controller:ControllerActivity.java
// Controller
public class ControllerActivity extends Activity {

private Button mBtn;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.Activity_view);

    // 在此处,controller调用并访问了view
    mBtn = (Button) findViewById(R.id.btn_hello_mvc);
    mBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            // 对于这个 OnClickListener,是属于view的,它是view的监听器
            // 在这里,view直接访问了model
            String btnClickData = ModelDataSource.ins().getBtnClickData();
            Toast.makeText(ControllerActivity.this, btnClickData, Toast.LENGTH_SHORT).show();
        }
    });

    // 在此处controller调用了model
    String btnText = ModelDataSource.ins().getBtnText();

    // 在此处controller设置了view的属性
    mBtn.setText(btnText);
}

}
最后则是Model:ModelDataSource.java
// Model
public class ModelDataSource {

private static ModelDataSource mInstance = null;

public static ModelDataSource ins() {
    if (mInstance == null) {
        synchronized (ModelDataSource.class) {
            if (mInstance == null) {
                mInstance = new ModelDataSource();
            }
        }
    }
    return mInstance;
}

private ModelDataSource() {
}

public String getBtnText() {
    // 在这里,
    // 我们可以去数据库中查找数据,
    // 也可以去网络中获取数据
    return "I am from ModelDataSource";
}

public String getBtnClickData() {
    // 在这里,
    // 我们可以去数据库中查找数据,
    // 也可以去网络中获取数据
    return "Hello MVC!";
}

}

model层很多人会理解为是普通的javabean以及我的大学老师也是这么和我说的,但是我并不这么认为,我不认为model只是很简单的一个数据结构定义,更多的它应该包含大量的数据处理和运算的逻辑,例如从数据库中采集数据的操作或者通过网络请求或者通过NetStream的方法来获取到二进制的数据,接着将这些二进制转换为我们设定好的javabean也就是我们定义好的抽象数据模型,然后该对象进行传递以及显示到视图ui上。
17年之前项目用MVC这种性质的项目结构是比较多的,M层封装网络请求的数据,在C层去调用数据然后呈现出来;
优点:Android开发中默认使用的框架,易于上手,能在不需要考虑太多需求的情况下快速开发一些小型demo功能app。
缺点:随着业务的扩展controller会变的越来越臃肿和复杂,大大增加了开发人员的维护成本以及交接成本,使得后期工作难以展开,且随着逻辑的复杂变化以及时间的推移会出现连开发人员自身都对当前代码逻辑的复杂造成错误的理解。

MVP介绍:


1516175204.png

从上图中我们可以很清晰的看到MVP与MVC中的区别:

从Controller变成了Presenter
去除View和Model之间的调用关系,从而彻底的分离了Model和View之间的关联与耦合
在MVP的架构中,有一个非常大的特点就是view和model之间的通信必须是通过presenter的传递,也正是因为这种隔离的关系,使得视图和数据之间的关系变得完全分离。

还是老规矩,我们分别来介绍一下MVP架构中的:Model(数据模型)、View(视图)、Presenter(主持者)他们三者的职责以及相互之间的关系到底是如何运作的。

View层:视图层,它所对应的不只是layout中的xml文件还包括了Activity/Fragment作为视图的显示。这样做是扩大了View层的职责所在,View不仅是设置ui的显示和属性并且还包括了生命周期的回调。

Presenter层:主持者层,它相当于是Controller中的业务逻辑部分,它主要是负责view和model层之间的通信,及时的响应view层的请求并主动的调用model层的数据获取,并且将获取到的数据结果返回给view层中。presenter是另外新建立一个class,并且让view从创建的时候就持有一个presenter的实例,当view发生某些请求响应或者生命周期发生变化,则会迅速的向presenter发起请求,让presenter做出响应的处理,比如:刷新数据、清除数据防止泄露等。

Model层:此处的数据抽象层model和MVC中的model层是一样的,这里就不做更多的叙述。

view和presenter两者之间的通信并不是想怎么调用就可以怎么调用的,他们之间有着一个标准的协议,就是在两者之间定义通用接口IContract,在这个interfac中定义了view层中要暴露的接口也定义了presenter层中需要暴露给view的接口,其目的是利用接口的方式将两者进行隔离,两者之间谁都不认识谁的实现,达到面向接口编程的目的:
简单的代码逻辑:
// Contract
public interface IContract {
interface View {

    void updateBtnText(String s);

    void showToast(String s);
}

interface Presenter {

    /**
     * 调用该方法表示presenter被激活了
     */
    void start();

    void loadClickString();

    /**
     * 调用此方法表示presenter要结束了
     * 其目的是为了接触相互持有导致的内存泄露
     */
    void destroy();
}

}
View层ViewActivity.java:
// View
public class ViewActivity extends Activity implements IContract.View {

private Button mBtn;
private IContract.Presenter mPresenter;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.Activity_view);

    // 在最开始的时候构建presenter
    mPresenter = new Presenter(this);

    // View初始化
    mBtn = (Button) findViewById(R.id.btn_hello_mvp);
    mBtn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            mPresenter.loadClickString();
        }
    });
}

@Override
protected void onStart() {
    super.onStart();
    mPresenter.start();
}

@Override
protected void onDestroy() {
    if (mPresenter != null) {
        mPresenter.destroy();
        mPresenter = null;
    }
    super.onDestroy();
}

@Override
public void updateBtnText(String s) {
    mBtn.setText(s);
}

@Override
public void showToast(String s) {
    Toast.makeText(this, s, Toast.LENGTH_SHORT).show();
}

}
Presenter层Presenter.java:
// Presenter
class Presenter implements IContract.Presenter {

private IContract.View mView;

Presenter(IContract.View view) {
    mView = view;
}

@Override
public void start() {
    String s = ModelDataSource.ins().getBtnText();
    mView.updateBtnText(s);
}

@Override
public void loadClickString() {
    String s = ModelDataSource.ins().getBtnClickData();
    mView.showToast(s);
}

@Override
public void destroy() {
    mView = null;
}

}
Model层ModelDataSource.java:
// Model
public class ModelDataSource {

private static ModelDataSource mInstance = null;

public static ModelDataSource ins() {
    if (mInstance == null) {
        synchronized (ModelDataSource.class) {
            if (mInstance == null) {
                mInstance = new ModelDataSource();
            }
        }
    }
    return mInstance;
}

public String getBtnText() {
    // 在这里,
    // 我们可以去数据库中查找数据,
    // 也可以去网络中获取数据
    return "I am from ModelDataSource";
}

public String getBtnClickData() {
    // 在这里,
    // 我们可以去数据库中查找数据,
    // 也可以去网络中获取数据
    return "Hello MVP!";
}

}

从代码上看我们可以发现比起传统的MVC从代码数量上看似乎并没有减少反而增加了不少的代码和接口,从逻辑上看似乎有些晕乎。但事实并非如此,当我们理解了MVP后则会发现这种调用方式其实是非常清晰的,因为你根本无需去在乎到底是谁在调用你,你只需要知道:我要让M做什么并且当M做完后我需要将M得出的结果告诉指定的V即可。同时在逻辑上的理解也是非常容易的。很显然,从时序图上我们可以看出其中的调用关系以及调用逻辑非常的清晰,并不会出现任何的跨道调用的现象,程序的执行过程是非常有条理性。 因为有Presenter这个角色的存在使得view部分的代码看上去是非常的清晰的,每一个方法都有它自己的主要倾向和职责所在,彼此之间并不会相互耦合。而Presenter中的代码也是如此,每一个方法都只处理一件事,并不会做其他无相关的事情。

由此我们可以得出一个结论: 对于view来说:
我需要一个主持者,当出现view事件的响应或者生命周期的变化时,我需要告诉这位主持,我要做些什么。
我会提供一系列通用接口,以便于当主持完成我的请求后,调用相应的接口让我明白这件事的结论是如何。
我所有的请求都发给主持,让他帮我做决定,但是这件事的决定是如何做,我并不知道,但我需要结果。
对于presenter来说:
我只会接收到请求后找model寻求帮助,等model做完事情后通知我了,我在把结果传递给view。
我只知道指挥model做事、让view显示数据,但我不干活。
我相当于一座桥,连接着view和model这两座岛,他们谁也不认识谁,想要通信必须要通过我,如果没有我,他们两永远都不会认识。

优点:
使用MVP可达到低耦合高内聚并且尽可能的保证了开闭原则,非常符合当前的软件工程;
由于模块间的耦合很小,可做并行开发,一边开发View,一边开发Model;
适合大部分的App,代码逻辑清晰易懂,大大降低开发、维护和交接成本;
视图和底层进行彻底的分离,View发生改变则只需要修改View部分代码,底层数据实现发生改变则只需要修改底层Model的代码。
缺点:对于很小的demo来说构建复杂和麻烦,不适合短期、小型且以后不在做任何维护的模块开发。

总结:
虽然MVP是一款非常优秀的架构,但是再优秀的架构也还是会有缺陷的。在技术的世界中,没有最完美的架构,只有最符合需求的架构,尽管再优秀在完美的架构也还是会有很多不足之处的。在MVP中也是存在不少的不足之处,例如:在构建View和Presenter的时候,我们需要多写大量的冗余接口,这无非是增加了额外的代码量。还有就是假设我需要新增方法或者修改某个方法的参数、返回值等,则至少需要变动3个以上的文件,View,Presenter,以及IContract接口。这些都是MVP的不足之处。但是往往我们不能因为某些可以容忍的不足而放弃,也许放弃可以加快眼前的步伐,但对于未来将深陷到难以自拔了。

上一篇下一篇

猜你喜欢

热点阅读