关于MVC、MVP架构

这里开始记录下来自己对MVC、MVP、MVVM这三种框架模式的理解,本文从以下几个方面来梳理。

 

  • 架构的目的
  • 框架模式、设计模式
  • MVC设计的介绍
  • MVC在Android中的应用
  • MVC该如何设计
  • MVP设计的介绍
  • MVP在Android中的应用

1. 架构的目的

当我们在进行OOP编程时,一切对象来源于需求,对象结合业务逻辑通过多态、继承、等封装成各个业务模块。我们通过设计将程序模块化,使模块内部高内聚和模块之间低耦合。这样做的好处是,当我们进行开发或者测试时,我们只需要专注于一点,而不用考虑牵一发动全身之类的问题,从而大大提高了我们的工作效率。但是设计是服务于目的的,就是说我们的需求决定着设计,不同的场景有些不同的需求,那么其应用的设计模式必然是不尽相同的。万不可为了设计而设计,导致忘记了设计的初衷。
我们举个例子,假使我们的程序只需要实现一个极其简单的功能以至于只有寥寥数行代码,那这时候去考虑引入设计模式是不切实际的。但相反,对于系列庞大、功能众多的app我们就必须考虑引入一套符合情景的模式以梳理开发、简化维护。


2. 框架、设计模式

框架和设计模式是完全的两个概念,框架通常是代码的重用,而设计模式是设计的重用,架构则介于两者之间,部分代码重用,部分设计重用。
可能以上说法比较晦涩难懂,在此摘录一些从业者对它们的理解。

  • 框架通常是代码重用,模式是设计重用;
  • 设计模式是思考的过程,框架是思考的结果;
  • 框架需要用到设计模式,比如我们封装了一套retrofit+okhttp+rxjava网络请求框架,我们可能用到很多设计模式。比如:代理模式、观察者模式等;
  • 脱离实际需求谈框架都是耍流氓。
  • 框架是面包、面条、饺子…,设计模式是做面包面条饺子的手艺(至于怎么做、做成什么形状、味道…主要看手艺);
  • 设计模式是大量场景总结出来的模版,如果你有相应的场景就可以往上套。

我们今天谈到的MVX系列是框架而不是设计模式,因为MVX框架是用来解决实际需求的(比如View、Model如何分离相互控制)。


3. MVC设计的介绍

这里写图片描述

MVC的全称是Model View Controller,如图是模型(model)-视图(view)-控制器(controller)的缩写。一种软件设计典范,用一种业务逻辑、数据、界面显示分离的方法组织代码,在改进和个性化定制界面及用户交互的同时,不需要重新编写业务逻辑。
其中M层处理数据,业务逻辑等;V层处理界面的显示结果;C层起到桥梁的作用,来控制V层和M层通信以此来达到分离视图显示和业务逻辑层。


4. MVC在Android中的应用

这里写图片描述

我们在文章的最开始说了框架的作用就是为了解决代码的臃肿,提高代码的复用率。在MVC模式中我们可以发现View、Model层都是遵循此原则设计的。
对于View层来讲,我们通常在做一个项目时封装出许多自定义控件或者动画,封装较好的情况下在别的项目中我们可以直接拿来用,这是View层代码复用的体现。
对于Model层呢?我们可以讲Model理解为用来存储业务数据,当我们的同一app需要跨平台时(比如腾讯视频的apk版和腾讯云极光TV版)我们考虑是不是Model可以共用?
对于Controller层来讲,几乎不可复用,因为它和业务逻辑联系太紧密。下面我们就来谈谈MVC在Android中的体现。

上图为MVC模式在Android项目中的体现,View代表视图层,Controller代表控制器,Model代表数据模型,app通过View视图监听控件根据控件表现调用相应的控制器处理业务逻辑,业务逻辑处理完之后控制器通知Model层改变,Model层改变带动View视图变化。
在android中一般采用xml文件进行界面描述,这些xml文件可以视为app的View层,引用很简单,并且通过良好构造也能达到一定的复用性。model对应着各种体现业务逻辑的数据类,它们体现了app的数据(对数据库、网络操作)。而联通它们之间的桥梁就是Controller即Activity、Fragment,我们通过在activity/fragment来管理控制整个业务流程。

这里举个例子来形象地阐述下MVC在Android中的应用,比如一个饭店的做饭流程,做饭就要考虑做什么?拿什么做?这其中是如何分工的。饭店老板戚总就好比我们的Controller,饭店的厨师老范相当于View(买什么菜他说了算是需求的来源),采购小王相当于Model。厨师老范需要做个霸王别姬,需要一大堆食材,他把需求报告给戚总,戚总整理成清单要求小王去采购,小王采购完成后直接将食材交给厨师老范。这就是MVC的一个循环,当然饭店比较小,有的时候厨师老范只需要一根葱这时候没必要专门让采购跑一趟了吧也省点油钱,戚总顺手就买了,时间久了,戚总慢慢干了点采购的活…

我们举个简单的例子。根据学生号码查询其名字、性别、年龄。

V层在xml文件中描述如下:
这里写图片描述

M层数据类如下:
这里写图片描述

C层如下:
这里写图片描述

到这里我们理解了MVC在android中的应用原理,那么我们进一步思考一下,如果app的业务逻辑相对简单,这么负责调控app的activity/fragment中的代码也相对简单,但当处理业务逻辑非常繁琐的app时,activity/fragment中的代码就会变得相当臃肿不雅,非常不利于维护。并且在实际开发过程中,我们经常在activity/fragment中写下这样的代码,如下

textView.setText(string);

这样便违反了MVC的原则,在C中处理V,使得C的职责单一性被破坏,所以这时候MVP模型便走进了我们的视野。下节我们先不讲MVP,当我们知道了MVC的现状后,我们该如何合理地应用MVC模型。


5. MVC该如何设计

MVC 虽然只有三层,但是它并没有限制你只能有三层。所以,我们可以将 Controller 里面过于臃肿的逻辑抽取出来,形成新的可复用模块或架构层次。

  • 将网络请求抽象到单独的类中;
  • 提供自定义控件封装到专门的类中;
  • 构造 ViewModel;
  • 专门构造存储类;

通过代码的抽取,我们可以将原本的 MVC 设计模式中的 ViewController 进一步拆分,构造出 网络请求层、ViewModel 层、Service 层、Storage 层等其它类,来配合 Controller 工作,从而使 Controller 更加简单,我们的 App 更容易维护。
另外,不知道大家注意到没,其实 Controller 层是非常难于测试的,如果我们能够将 Controller 瘦身,就可以更方便地写 Unit Test 来测试各种与界面的无关的逻辑。移动端自动化测试框架都不太成熟,但是将 Controller 的代码抽取出来,是有助于我们做测试工作的。


6. MVP设计的介绍

还记得我们上面的饭店吧,时间久了戚总有时候为了多省点油钱越来越多的担任采购的活了,甚至还兼职抄起了菜…慢慢的随着饭店的生意越来越好慢慢做大,戚总的事情越来越多开始忙不过来了,另一方面饭店的生意好了却不见得多挣了多少钱。戚总想着会不会是厨师老范和采购小王串通坑他呢,于是乎,戚总想了一夜,哎嗨,一套新的管理机制来了。
View还是厨师老范,Model是采购小王,戚总把自己定位为Presenter,以后厨师老范有需求了仍然告诉戚总,戚总安排给小王去采购,采购完之后把食材交给戚总检查,数目账单没问题了戚总再将食材给厨师老范。这样不让厨师老范和采购小王就没有任何机会沟通避免他们串通,同时戚总以后只负责吩咐两人做事也不为了省点油钱自己亲自干了毕竟生意做大了得像个老板的样子了,由此MVP模型初见雏形。其示意图如下:

这里写图片描述

这样做它有何利弊呢?我们仍然结合饭店的例子加以说明。

首先使它的好处:

  • 分工明确,大家各司其职就好提高效率;
  • 利于以后的发展,比如要裁人换人等,像MVC模式我如果换掉厨师,就会同时影响老板和采购的工作,MVP呢不管如何调整我们只调整P就行,其他模块互不干涉影响。
  • 大大解放老板的工作(是P层支付者调度减少代码臃肿)。
  • 用程序语言就是更加高内聚、低耦合;

再来谈谈它的弊端:

  • 结构复杂了。

总体而言,对于业务逻辑复杂的项目,MVP的利要远大于弊。


7. MVP在Android中的应用

下面我们就来谈谈MVP在Android之中的应用,既然是在Android中的应用,那么我们就考虑从大哥那儿学一套本事%>_<%。
这是Google大哥的官方demo。
再来一篇分析demo的文章。

研究完了上面的,我们是时候来点实践了。就模拟个登录功能吧。
首先看清文件结构:

这里写图片描述

google应用了BaseView、BasePresenter两个类。 如下:

这里写图片描述

这里写图片描述

这两个类作为所有view和presenter的基类来使用。
BasePresenter中的start方法是用来load页面时加载相应数据的,而登录模块暂时并不需要该方法,但是这个类毕竟是为整个业务模块服务的,别的业务可能需要,暂时保留。
BaseView中的setPresenter方法是为了向fragment中传递activity中new出来的presenter对象。登录模块其实一个activity足以搞定,这个方法多余,但是保留该方法,理由同上。

开始具体业务。这里需要构建view和presenter。值得注意的点是,google将view和presenter放到了一个契约类中了。所以

这里写图片描述

View中提供了四个相关操作,即登录成功、登录失败、获取用户名、获取密码;
Presenter中只提供了login()登录操作。

这里写图片描述

这里activity作为mvp中的view层实现了契约类中的view接口。

这里activity作为mvp中的view层实现了契约类中的view接口。

public class LoginActivity extends Activity implements LoginContract.View{

    private Context mContext;
    private EditText mNameEt, mPasswordEt;
    private Button mLoginBtn;
    private LoginContract.Presenter mPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.login_activity);
        mContext = this;
        mPresenter = new LoginPresenter(this);
        mNameEt = (EditText) findViewById(R.id.nameEt);
        mPasswordEt = (EditText) findViewById(R.id.passwordEt);
        mLoginBtn = (Button) findViewById(R.id.loginBtn);
        mLoginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mPresenter.login();
            }
        });
    }

    @Override
    public void loginSuccess() {
        Toast.makeText(mContext, "登录成功", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void loginFailed() {
        Toast.makeText(mContext, "登录失败", Toast.LENGTH_SHORT).show();
    }

    @Override
    public String getLoginName() {
        return mNameEt.getText().toString();
    }

    @Override
    public String getLoginPassword() {
        return mPasswordEt.getText().toString();
    }

    @Override
    public void setPresenter(Object presenter) {

    }

}

注意,如果你是fragment作为view,一定要加上mView.setPresenter(this)把P传递过去。

public class LoginPresenter implements LoginContract.Presenter{

    private LoginContract.View mView;

    public LoginPresenter(LoginContract.View view){
        mView = view;
    }

    @Override
    public void login() {
        boolean successFlag = httpLogin(mView.getLoginName(), mView.getLoginPassword());
        if ( successFlag )
            mView.loginSuccess();
        else
            mView.loginFailed();
    }

    @Override
    public void start() {

    }
}

Google通过一个契约类使我们的项目结构更加清晰。
当然没有绝对的MVP、MVC框架,比如在上面的登录功能中我们利用MVC更加方便简单,但是如果考虑以后的扩展性(比如增加第三方登录等等)利用MVP更加合适。所以无论选择哪种方式必定是建立在需求的基础上的

 

来源: http://www.cnblogs.com/chenxibobo/p/6894244.html