Android中用到的MVP模式

news/2025/2/23 14:02:59

参考:android架构设计—mvp模式封装

MVP模式是由MVC模式逐渐演化出来的。首先简单介绍一下MVC。这个在Spring框架里面是一个很常见的模式。

MVC
M(model)模型, 是应用程序中用于处理应用数据逻辑的部分,通常模型对象负责在数据库中进行存取

V(view)视图, 是应用程序中处理数据的显示部分,通常视图是一句模型数据来创建的

C(controller)控制器, 是应用程序中处理用户交互的部分,通常控制器负责从视图读取数据,控制用户的输入,并向模型发送数据。

MVP
M(model)负责数据的请求,解析,过滤等数据操作

V(View)负责图示部分展示,图示事件处理,Activity,Fragment,Dialog,ViewGroup等呈现视图的组件都可以承担该角色

P(presenter)是View和Model交互的桥梁。

 

这样看的话,MVP与MVC似乎差不多哈,但是他俩是有区别的,下图可以很明显的看出两者之间的区别。

最大的一点在于持有关系上,MVP呢是Presenter 持有View 和Model 作为两者的桥梁,这样也是理所应当的。同时View只持有Presenter。和Model没有任何关系。那么这样的话,业务就是业务,很纯粹。而View毕竟是视图方面的,与用户交互相关,那么用户交互了就得涉及相关的业务处理,所以持有presenter,让其作为桥梁控制Model的业务。所以一切业务相关的界面变化,估计也是在Presenter里面连接进行间接控制的。说白了就是View与Model无联系无联系!

但是MVC呢就不一样了,Model持有View,View持有model,View还持有Controller, Controller又持有model,这样的话在View这里就已经直接通过Model来控制业务逻辑了,这各种关系我脑子里比较迷糊反正觉得有一丢丢乱。回想我之前工作做的项目,,好像就是这种。Activity里面顺带带了业务处理,乱乱的无章法。。。

 

 

一句话总结:你代码逻辑有没有写在View中的,有就是MVC,没有就是MVP

上面链接里面说的很直白

MVP:View不直接与Medel进行交互,而是通过presenter来与Model交互,来达成的一种间接交互。

presenter与View的交互是通过接口来进行的,更有利于添加单元测试

通常View与Precenter是一对一的,但是复杂的view可以绑定多个Presenter来处理逻辑。

 

MVC:View是可以与Model直接交互的

Controller是基于行为的,并且可以被多个view共享

可以负责决定显示哪个view;

 

到了这里,没有一个例子显然是不太有说服力的,MVP到底是个什么样子呢?代码袭来。。。

关于简单登录的案例
首先看下结构:

接口:M方面

package com.example.forev.mvpprojectdemo.model;
 
/**
 * Created by forev on 2018/7/8.
 * 定义一个model总接口
 */
 
public interface IModel {
}

接口: V 方面:

package com.example.forev.mvpprojectdemo.view;
 
/**
 * Created by forev on 2018/7/8.
 * 定义一个view总接口
 */
 
public interface IView {
}

父类:Presenter方面,表现持有关系。

package com.example.forev.mvpprojectdemo.presenter;
 
import com.example.forev.mvpprojectdemo.model.IModel;
import com.example.forev.mvpprojectdemo.view.IView;
 
import java.lang.ref.WeakReference;
 
/**
 * Created by forev on 2018/7/8.
 * 所有presenter的父类,因为presenter会持有View 以及Model部分,所以
 * 索性就写到总父类里面去吧
 */
 
public class PresenterFather {
 
    protected IModel mIModel;
 
    //此处View个人感觉最好用一个弱引用。
    protected WeakReference<IView> mViewReference;
}

好,接口相关的关系定义完了,接下来就看看MVP 怎么表现的。

M实际逻辑:

package com.example.forev.mvpprojectdemo.model;
 
import com.example.forev.mvpprojectdemo.model.Lisentener.LoginLisentener;
 
/**
 * Created by yayali on 2018/7/8.
 * model 负责的是数据以及业务逻辑
 */
 
public class LoginMode implements IModel {
    //model 负责数据以及业务逻辑。
    private String mUserName = "yayali";
    private String mPassWord = "123";
 
    public void login(String username, String password, LoginLisentener lisentener) {
        if (lisentener == null) {
            return;
        }
        if (mUserName.equals(username) && mPassWord.equals(password)){
            lisentener.onSeccess();
        } else {
            lisentener.onFails();
        }
    }
}

以上可以看出,这个Model里面只有数据和登录的逻辑,就算算出了结果,也用一个回调,回调出去了。写的是相当封闭了。与View无任何关系。

View实际逻辑,首先先写一个根据距离的情景得出的接口。例如总得知道用户在用户名密码编辑框里输入了什么信息吧。这个是必须在View里面获取的。然后,登录成功或者失败总得能给个提示之类的吧。这些具体的提示也是在View里面需要做的。那么写了这几个方法,很明显就是给Presenter来调用的。因为Presenter会作为一个桥梁,来控制View和model之间的联系。从Model那里计算出登录结果,然后控制下面的接口,做相关调用。
 

package com.example.forev.mvpprojectdemo.view;
 
/**
 * Created by forev on 2018/7/8.
 * 写了一个接口继承自IView,这点关系很重要
 */
 
public interface ILoginView extends IView {
    String getUserName();
    String getPassword();
    void onLoginSeccess();
    void onLoginFails();
}

首先写了一个关于View的接口!为什么呢?因为Presenter是个桥接角色,那么它势必会调用Model的业务逻辑,得出结果,然后再操纵其持有的View来进行View上的变更。但是界面呀,毕竟都在Activity里面呆着呢。实际界面变化在Activity里做最合适的。所以就专门写了接口,专门获取用户输入信息,以及界面变化回调。

下面真正的View:
 

package com.example.forev.mvpprojectdemo.activity;
 
import android.content.Context;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
 
import com.example.forev.mvpprojectdemo.R;
import com.example.forev.mvpprojectdemo.presenter.LoginPresenter;
import com.example.forev.mvpprojectdemo.view.ILoginView;
 
/**
 * LoginActivity展现了一个界面,尤其是它实现了ILoginView,代表他是MVP中的V
 */
public class LoginActivity extends AppCompatActivity implements ILoginView{
    private EditText mUserNameEdit;
    private EditText mPasswordEdit;
    private Button mLoginBtn;
 
    private LoginPresenter mPresenter;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setView();
        setData();
    }
 
 
 
    private void setView() {
        this.mUserNameEdit = findViewById(R.id.login_act_edit_user_name);
        this.mPasswordEdit = findViewById(R.id.login_act_edit_user_pass);
        this.mLoginBtn = findViewById(R.id.login_act_btn_login);
 
        mLoginBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //隐藏软键盘
                InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
                imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
 
                mPresenter.login();
            }
        });
    }
 
    private void setData() {
        this.mPresenter = new LoginPresenter(this);
    }
 
    @Override
    public String getUserName() {
        return mUserNameEdit.getText().toString();
    }
 
    @Override
    public String getPassword() {
        return mPasswordEdit.getText().toString();
    }
 
    @Override
    public void onLoginSeccess() {
        Toast.makeText(getApplicationContext(), "登陆成功!", Toast.LENGTH_LONG).show();
    }
 
    @Override
    public void onLoginFails() {
        Toast.makeText(getApplicationContext(), "登录失败!", Toast.LENGTH_LONG).show();
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //记得在销毁的时候断掉引用链,养成良好的习惯
        this.mPresenter = null;
    }
}

看吧,界面上没有任何具体的业务逻辑,业务逻辑相关的都交给Presenter去处理了,View顶多就是在回调里面做做界面响应啥的。

Presenter:连接View和Model的桥梁

package com.example.forev.mvpprojectdemo.presenter;
 
import com.example.forev.mvpprojectdemo.model.Lisentener.LoginLisentener;
import com.example.forev.mvpprojectdemo.model.LoginMode;
import com.example.forev.mvpprojectdemo.view.ILoginView;
import com.example.forev.mvpprojectdemo.view.IView;
 
import java.lang.ref.WeakReference;
 
/**
 * Created by forev on 2018/7/8.
 */
 
public class LoginPresenter extends PresenterFather {
 
    public  LoginPresenter(ILoginView loginView) {
        this.mIModel = new LoginMode();
        this.mViewReference = new WeakReference<IView>(loginView);
    }
 
    public void login() {
        if (mIModel != null && mViewReference != null && mViewReference.get() != null) {
 
            ILoginView loginView = (ILoginView) mViewReference.get();
            String name = loginView.getUserName();
            String passWord = loginView.getPassword();
            loginView = null;
            //此时LoginListener作为匿名内部类是持有外部类的引用的。
            ((LoginMode)mIModel).login(name, passWord, new LoginLisentener() {
                @Override
                public void onSeccess() {
                    if (mViewReference.get() != null) {
                        ((ILoginView)mViewReference.get()).onLoginSeccess();
                    }
                }
 
                @Override
                public void onFails() {
                    if (mViewReference.get() != null) {
                        if (mViewReference.get() != null) {
                            ((ILoginView)mViewReference.get()).onLoginFails();
                        }
                    }
                }
            });
        }
    }
}

这样看Presenter就是根据用户的操作行为来决定怎么调业务逻辑,并且根据业务逻辑的结果再决定怎么调用View。

接下来是Lisentener:

package com.example.forev.mvpprojectdemo.model.Lisentener;
 
/**
 * Created by forev on 2018/7/8.
 */
 
public interface LoginLisentener {
    void onSeccess();
    void onFails();
}

代码大概就是这样的,重点就是持有关系,以及各自的逻辑要拎得清。。presenter就得 持有View和Model,并且View就是界面展现,Model就是纯业务逻辑。View和Model无任何联系。那么为什么要这么写呢?有什么优点呢?

 

MVP的优缺点
优点: 单一职责, Model, View, Presenter只处理单一逻辑

解耦:Model层的修改和View层的修改互不影响

面向接口编程,依赖抽象:Presenter和View互相持有抽象引用,对外隐藏内部实现细节。

可能存在的问题:

Model进行一步操作的时候,获取结果通过Presenter会传到View的时候,出现View引用的空指针异常。

Presenter和View相互持有引用,解除不及时的话容易出现内存泄漏。
 

 

 

 

 

 

 

 

 

 


http://www.niftyadmin.cn/n/4479902.html

相关文章

C语言之#define

文章目录一.什么是#define二.#define的一般形式三.#define如何工作&#xff1f;四.终止宏 #undef五.注意六.define与const区别一.什么是#define C语言中&#xff0c;可以用 #define 定义一个标识符来表示一个常量。 特点是&#xff1a;定义的标识符不占内存&#xff0c;只是一…

C语言基本数据类型int, short int, long int, long long int, unsigned int, signed int等解析

一. 普通int类型 int类型是有符号整型&#xff0c;即int类型的值必须是整数&#xff0c;可以是正整数&#xff0c;负整数&#xff0c;零。 int类型取值范围因计算机系统而异。早起的16位IBM PC兼容机使用16位来存储一个int值&#xff0c;其取值范围是-32769 &#xff5e;32768…

C语言 getchar()原理及易错点解析

文章目录一.getchar()系列1.getchar()工作原理及作用2.使用getchar()清理回车\n3.使用getchar()清理缓存4.混合scanf()与getchar()一.getchar()系列 1.getchar()工作原理及作用 工作原理&#xff1a;getchar()是stdio.h中的库函数&#xff0c;它的作用是从stdin流中读入一个字…

C语言之 指针与多维数组最强解析

假设有以下声明&#xff1a; int multiArray [4] [2] //声明一个int类型的二维数组数组名multiArray是该数组首元素( multiArray[0] )的地址。 在本例中&#xff0c;multiArray的首元素是一个内含两个int值的数组&#xff0c;所以multiArray是这个内含两个int值的数组的地…

C语言字符串的输出与输入学习笔记

文章目录字符串的输入与输入1.字符串初始化1⃣️&#xff1a;用足够的空间的数组存储字符串&#xff1a;2⃣️&#xff1a;省略数组初始化声明中的大小2.数组与指针1⃣️&#xff1a;指针创建字符串2⃣️&#xff1a;数组与指针的区别3⃣️&#xff1a;使用指针的优缺点3.scanf…

C语言fgets()与fputs()详解

文章目录fgets()与fputs()1⃣️fgets()优缺点&#xff1a;2⃣️fgets()返回值&#xff1a;3⃣️fgets()操作实例&#xff1a;3⃣️fgets()操作进阶&#xff1a;fgets()与fputs() fgets()函数的第二个参数指明了读入字符的最大数量。如果该参数为n&#xff0c;那么fgets函数将读…

深入了解JVM—什么是虚拟机

我们都知道在 Windows 系统上一个软件包装包是 exe 后缀的&#xff0c;而这个软件包在苹果的 Mac OSX 系统上是无法安装的。类似地&#xff0c;Mac OSX 系统上软件安装包则是 dmg 后缀&#xff0c;同样无法在 Windows 系统上安装。 为什么不同系统上的软件无法安装&#xff0c…

Java问题:”接口内部创建类“与”类内部创建接口“分别有什么用

两种形式&#xff0c;到底各自都有什么用处&#xff1f;&#xff1f; class A {interface B {} }interface C {class D {} } 文章目录一.接口内部创建类1⃣️.用法12⃣️.用法23⃣️.如何创建对象及调用方法二.类内部创建接口一.接口内部创建类 1⃣️.用法1 如果一个类的功能…