zoukankan      html  css  js  c++  java
  • MVP模式是你的救命稻草吗?

    为什么要学习架构?

    不管是MVC还是MVP,亦或则其他架构,它们的设计目的都是为了达到编码的最高境界,那就是:低藕合,高复用,易测试,好维护。

    而要达到这个终极目标,首先要理解的是每个部分各自负责些什么,以及如何组合在一起。因此我个人认为,学习架构关键在两步:

    1. 如何把缠在一起的代码拆分
    2. 如何把拆开的代码再组合

    很多新手在刚做项目时,都会把所有的代码,如数据的访问和处理,数据的展示,用户的输入都写在一起,代码和思维都呈现出一种线性的形式,一切都是直来直往的。这样代码量确实少,写的时候也觉得方便,但是带来了几个致命的问题:

    1. 当业务越来越复杂时,修改代码成了噩梦。同样的逻辑放在不同的地方,本来只用改一处的变成了需要改几百处。而又因为所有的逻辑都互相牵扯住,导致本来只想改一处的,结果却影响了几百处。
    2. 当时间越来越遥远时,理解代码成了噩梦。本来只需要阅读几行的时候,却因为所有的代码都杂在一起变成了要阅读几千行。时间一长,重新阅读时,别说别人,就是自己也很难一下就能掌握关键点。
    3. 当需要做一个功能时,却发现代码无法复用,明明是一样的逻辑也只能靠Ctrl+CCtrl+V。这又为今后修改代码时增加了工作量。
    4. 当需要测试时确发现很难进行测试,每次修改一处代码后都必须进行重复的人工测试,无法进行自动化测试,模块和模块也无法拆开来进行独立的单元测试,每次都要整体的测一遍才能确保一切完好如初。

    要换的不是架构,而是思维方式

    其实目前市面上的架构模式已经有很多种,各有不同,但模式终究只是一种设计理念的表现形式,学习再多的架构,你也只是多会用了几种工具而已,学习一种模式其实是在学一种思维方式:

    如何在解决问题的时候把问题合理的拆分,又如何将拆分的零件合理的组装成解决问题的工具。

    将这种思维方式深入到你的大脑里,血液里,直至骨髓,让它成为你思考问题的一种习惯和定式,逐渐成为你的一部分,那么这时你已达到无招胜有招的境界了。

    闲话先不扯了,回正题。

    实现架构没有唯一标准

    这里首先需要说明的是无论MVP或MVC还是MVVM等任何一种架构和模式其实都没有谁优谁劣之分,而且就算是同一种架构,也可以根据不同的使用场景来做不同的实现方式,这里并没有宇宙绝对的对错标准和概念定义。这和张三丰在教无忌太极拳以后让其先忘掉招式是一样的道理,在应用型领域,定式和概念只应是在学习的过程中才存在的,而真正学会以后应该马上忘掉定式,忘掉概念,将其用熟用活才是关键。

    所以说我在此描述的概念也不是唯一的标准,而只是个人对其的理解。

    什么是MVC

    因为MVP是MVC的一个变种,因此我们先来看在MVC里代码是如何拆分和组合的,这里简要的描述常见的定义:

    1. 拆分:Model负责数据,View负责显示,Controller负责用户输入。
    2. 组合:View将用户操作和事件传递给Controller,Controller将用户命令翻译成消息来传递给Model,Model在处理完数据后,通知View来显示给用户,而View又从Model取出数据。

    我认为在学习MVP之前,必须深刻理解什么叫做MVC,因为两者的区别其实没有你想象中的那么大,与其神话并盲目追崇新的架构,期望其能解脱你于苦海,还不如深刻的理解MVC的设计理念,这就和学习一门新语言一样,只有你真正深刻的理解了其思维方式,那么学习新的一门语言其实都是易如反掌的。因为它们其实都是在做一件事,只是做的过程上有些许差异而已。

    更多MVC的理解,可以参考我之前写的一篇文章:在谈MVP之前,你真的懂MVC吗?

    什么是MVP

    MVP与MVC最大的区别就在与将Model和View通过Presenter隔开了,不再允许其互相直接通信,而所有的消息都是通过Presenter这个中间人来传递。

    而这样做的目的主要是为了将数据和展示划出更明确的界限。

    首先我们来看MVP到底是在解决什么问题的:

    • 数据管理
      1. 什么是数据?(Model)
      2. 如何筛选数据?(Selections)
      3. 如何改变数据?(Commands)
    • 用户界面
      1. 如何展示数据?(View)
      2. 如何通过事件来改变数据?(Interactor)
      3. 如何将这些放在一起?(Presenter)

    可以看出其实一个GUI程序的核心,还是在于用户和数据之间的关系。而架构的引入也是为了解决这几个核心的问题。

    那么MVP又是如何解决这几个问题的,Model,View,Presenter三者又各自负责什么呢?谁来负责请求网络数据,访问和存储本地数据,谁来负责处理数据,谁来负责显示数据,谁又来负责和用户交互呢?

    具体表现在代码上,也可以说:网络请求应该放在哪,数据库访问应该放在哪,业务逻辑应该放在哪里,处理用户输入应该放在哪,谁又来控制显示或隐藏View的等具体的细节问题。

    带着这些具体问题,我们一起来学习。

    如何拆分

    首先来看MVP各自负责什么:

    1. Model,负责定义数据(解决什么是数据)
    2. Presenter, 负责在Model和View之间,从model里取出数据,格式化后在View上展示(解决如何把数据和用户界面放在一起)。
    3. View,负责担任一个被动界面,用于展示数据。(解决如何展示数据)

    和MVC比较而已,这里出现一个最大的疑问就是:那么谁来负责和用户的操作交互呢?答案是,用户在View上的所有操作(事件)都由View路由给Presenter,由Presenter来与其交互。

    而和MVC最大的区别在于Model和View完全被Presenter隔开了,Presenter作为它们之间的中间人去传递所有数据。

    如何组合

    三者又是如何组合起来的呢?

    很显然Presenter作为中间者,它是同时拥有View和Model的引用的,为了在它们之间起到桥梁作用,即Presenter会主动和View和Model进行通信

    Model和View必须是完全隔离的,不允许两者之间互相通信,保持对彼此的不感知,这样的好处是你彻底将数据和展示分离来开,并且可以独立的为Model去做测试。

    Model在三者中是独立性最高的,Model不应该拥有对View的引用,而且Model也不需要保存对Presenter的引用,对于Presenter而已,Model只需要提供接口,等着Presenter来调用时返回相应数据即可,这和经典MVC模式中是非常不同的,在MVC中Model在数据发送变化后,是需要发送广播来告之View去更新用户界面的,而在MVP中,Model是不应该去通知View,而是通知Presenter来间接的更新View的。

    Presenter和Model的关系也应该是基于接口来通信,这样才能把Model和Presenter的耦合度也降到最低,那么在需要改变Model内部实现,甚至彻底替换Model的时候,Presenter则是无需随之改变的。这样做带来的另一个好处就是你可以通过Mock一个Model来对Presenter以及View做模拟测试了,从而提高了可测试性。

    那么View和Presenter的关系呢?View是需要拥有对Presenter的引用,但仅仅是为了将用户的操作和事件立即传递给Presenter,为了让View和Presenter耦合较低,View也只应该通过接口与Presenter通信,从而保证View是完全被动的,一方面它由用户的操作触发来和Presenter通信,另一方面它完全受Presenter控制,唯一需要做的事情就是如何展示数据。

    简要总结三者之间的关系是:View和Model之间没有联系,View通过接口向Presenter来传递用户操作,Model不主动和Presenter联系,被动的等着Presenter来调用其接口,Presenter通过接口和View/Model来联系。

    View <- 接口 <- Presenter ->接口 -> Model

    View -> 接口 -> Presenter <- 接口 <- Model

    Talk is cheap, show me the code

    为了便于理解,这里提供一些伪代码加注释演示一下如何实现MVP模式:

    View

    interface IUserView {
    
      void setPresenter(presenter);
      void showUsers(users);
      void showDeleteUserComplete();
      void showDeleteUserError();
    
    }
    
    class UserView implements IUserView {
    
      UserPresenter presenter;
    
      // 保持对Presenter的引用,用于路由用户操作
      void setPresenter(presenter) {
          this.presenter = presenter;
      }
    
      // 将Presenter传递来的数据展示出来
      void showUsers(users) {
          draw(users);
      }
    
      // Model操作数据成功后,通过Presenter来告之View需要更新用户界面
      void showDeleteUserComplete() {
          alert("Delete User Complete");
      }
    
      // Model操作数据失败后,也是通过Presenter来告之View需要更新用户界面
      void showDeleteUserError() {
          alert("Delete User Fail");
      }
    
      // 当用户点击某个按钮时,将用户操作路由给presenter,由presenter去处理
      void onDeleteButtonClicked(event) {
          presenter.deleteUser(event);
      }
    
    }

    Model

    interface IUserModel {
    
      List<User> getUsers();
      boolean deleteUserById();
    
    }
    
    class UserModel implements IUserModel {
    
      // 在数据库里查找数据,并将数据返回给presenter
      List<User> getUsers() {
           return getUsersInDatabase(id);
      }
    
      // 在数据库里删除数据,并将结果返回给presenter
      User deleteUserById(id) {
          return deleteUserByIdInDatabase(id);
      }
    
    }

    Presenter

    interface IUserUserPresenter {
    
      void deleteUser(event);
    
    }
    
    class UserUserPresenter implements IUserPresenter {
    
      // 保持对View的引用
      IUserView view;
      // 保持对Model的引用
      IUserModel model;
    
      UserUserPresenter(IUserView view, IUserModel model) {
        this.view = view;    
        this.model = model;
    
        this.view.setPresenter(this);   
      }
    
      void start() {
        // 从Model中取出数据
        List<User> users = model.getUsers();
        // 将数据发送给View,让其展示到用户界面
        view.showUsers(users);
      }
    
      void deleteUser(event) {
        // View将用户操作路由过来,由Presenter来处理
        long uid = whichUserNeedToDeleteBy(event);
        // 将用户操作翻译成命令或消息传递给model,以改变数据
        boolean success = model.deleteUserById(uid);
        // 将Model操作数据后的结果通知View来改变用户界面
        if (success) {
              view.onDeleteUserSuccess();
        } else {
            view.onDeleteUserFail();  
        }
      }
    }

    OK,到此一个最简单的MVP模式就实现了,可以看到整个架构的轮廓已经很清晰了,而且面向接口编程也带来了很多的好处,让代码之间藕和较少,对测试支持也更为友好,理解和维护起来也更加方便了。

    以后有时间再为大家描述如何在android里面实现MVP模式,以便更具体的理解。有疑问欢迎参与讨论,大家一起学习进步。

    参考文档

    1. Model–view–presenter WIKI
    2. MVP: Model-View-PresenterThe Taligent Programming Model for C++ and Java
  • 相关阅读:
    Swift实现单例
    UIViewContentMode说明
    打开或关闭Mac的隐藏文件的命令。
    struts2-2.3.20以上版本无法正常启动
    eclipse下导入jdk源码
    js 与css script
    eclipse导入jquery包后报错
    ${pageContext.request.contextPath} :JSP取得绝对路径方法
    小米暑期实习在线笔试2015-04-25
    android动态污点分析
  • 原文地址:https://www.cnblogs.com/ldq2016/p/6694415.html
Copyright © 2011-2022 走看看