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

嗯 最近比较休闲了,先泡一杯碧螺春定定神,然后开始总结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的不足之处。但是往往我们不能因为某些可以容忍的不足而放弃,也许放弃可以加快眼前的步伐,但对于未来将深陷到难以自拔了。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,254评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,875评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,682评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,896评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,015评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,152评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,208评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,962评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,388评论 1 304
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,700评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,867评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,551评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,186评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,901评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,142评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,689评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,757评论 2 351

推荐阅读更多精彩内容

  • 和MVC框架模式一样,Model模型处理数据代码不变在Android的App开发中,很多人经常会头疼于App的架构...
    ppice阅读 4,307评论 2 17
  • Android App的设计架构:MVC,MVP,MVVM与架构经验谈1. 架构设计的目的1.1 通过设计使程序模...
    天空在微笑阅读 4,145评论 1 20
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,900评论 25 707
  • 冬天,不需要风,不需要雨,不需要雪,依旧可以冷的发颤。我自己确实不喜欢。我家在南方,对于下雪来说,机率渺茫。可是...
    灯德阅读 160评论 0 0
  • 愿你所有的努力都不会白费 愿你纷扰过后能梦想成真 愿你你去自己喜欢的城市 愿你买下自己喜欢的衣服 愿你过上自己喜欢...
    沐抒阅读 149评论 0 0