【Android】MVP架构模式

【Android】MVP架构模式

文章目录

  • 【Android】MVP架构模式
    • 🍿1.MVP架构原理
      • [1.1 各组件职责](#1.1 各组件职责)
      • [1.2 MVP的两种主要变体](#1.2 MVP的两种主要变体)
        • [1.2.1 被动视图](#1.2.1 被动视图)
        • [1.2.2 监督控制器](#1.2.2 监督控制器)
    • [🍕2 实现简单登录DEMO](#🍕2 实现简单登录DEMO)
      • [2.1 定义接口](#2.1 定义接口)
      • [2.2 实现Model和Presenter](#2.2 实现Model和Presenter)
      • [2.3 View层实现](#2.3 View层实现)
      • [2.4 相关布局文件](#2.4 相关布局文件)
    • [🌭3. 结论](#🌭3. 结论)

🍿1.MVP架构原理

MVP(Model-View-Presenter)是一种将应用程序分为三个主要组件的架构模式,这三个组件各自承担不同的职责:

Model(模型) :负责数据的存储、检索和业务逻辑处理
View(视图) :负责用户界面的展示和用户输入的接收
Presenter(表示器) :作为Model和View之间的中介,处理用户输入,操作Model并更新View

MVP架构的核心思想是实现关注点分离,将用户界面逻辑与业务逻辑明确区分,从而提高代码的可维护性和可测试性。各组件关系如下图:

1.1 各组件职责

  • Model:负责处理数据的获取、存储和管理,通常包含数据访问层(如数据库、API等)和实体模型

  • View:负责展示UI并与用户交互。View是一个"被动视图"(Passive View),它不会包含任何业务逻辑,只接受Presenter的指令来更新界面。

  • Presenter:是MVP的核心组件,负责处理业务逻辑。Presenter从Model获取数据,并将其传递给View,同时监听View的用户操作并处理相应的逻辑。

通过引入Presenter层,彻底实现了Model、View和Presenter三层的解耦,与MVC相比,MVP最大的不同点在于View和Model之间没有直接交互,而是通过Presenter相链接。Presenter通过接口与View和Model通信,保证了各组件之间的低耦合性,从而使代码更易于维护和测试。

1.2 MVP的两种主要变体

1.2.1 被动视图

这就是上面详细说明的,也是MVP中最常见的版本

  • 特点:View 极其"笨拙"和被动。它除了显示和转发用户输入外,几乎什么都不做。
  • 所有表现逻辑都在 Presenter 中。包括决定哪个UI元素显示/隐藏。
  • 优点:View 和 Model 完全解耦,可测试性最高。
  • 缺点:Presenter 可能会变得庞大,因为它要处理大量细微的UI状态。
1.2.2 监督控制器

其实,监督控制器就是Presenter的另一种形象称呼,它强调 Presenter 同时作为"监督者"和"控制器"的双重角色。

控制器:

View层只负责用户点击、滑动等操作,然后立即通知给Presenter层处理。比如"用户点击了登录按钮,用户名是XXX,密码是XXX,接下来你来处理。"

Presenter层接到指令后,开始发挥作为控制器的职责:

它决定调用哪个Model层的方法来实现具体的业务逻辑。

监督者:

  • 监听结果:当Presenter将任务交给Model层后,它并不会一直等待,而是注册一个监听器,监督Model层任务的完成情况
  • 处理结果:当Model层完成任务后(有可能成功也有可能失败),会通过回调(监听器)通知Presenter,这时,监督者开始工作:
    • 处理Model返回的数据或处理异常
    • 根据处理结果,监督者Presenter会指挥View层,给它一个明确的命令,比如"显示登陆成功界面"或者"显示错误信息"
  • 特点:View 层稍稍"聪明"一些。它可以处理一些简单的、不涉及业务逻辑的UI数据绑定。
  • 优点:减轻了 Presenter 的负担,Presenter 更专注于流程控制。
  • 缺点:View 和 Model 之间存在一定的耦合(通过数据模型),可测试性不如被动视图。

🍕2 实现简单登录DEMO

2.1 定义接口

首先,为了保证Presenter与View和Model的解耦,我们需要为View和Model定义接口。

java 复制代码
// LoginContract.java
package com.example.mvplogin.contract;

public interface LoginContract {

    interface View {
        void showLoading();
        void hideLoading();
        void showLoginSuccess(String username);
        void showLoginError(String message);
        String getUsername();
        String getPassword();
    }

    interface Presenter {
        void login();
        void attachView(View view);
        void detachView();
    }
}

2.2 实现Model和Presenter

模型层(LoginModel.java)

java 复制代码
// LoginModel.java
package com.example.mvplogin.model;

public class LoginModel {

    // 模拟用户数据库
    private static final String VALID_USERNAME = "admin";
    private static final String VALID_PASSWORD = "123456";

    public void login(String username, String password, OnLoginFinishedListener listener) {
        // 模拟网络请求延迟
        new android.os.Handler().postDelayed(() -> {
            if (username.isEmpty() || password.isEmpty()) {
                listener.onError("用户名或密码不能为空");
            } else if (!username.equals(VALID_USERNAME)) {
                listener.onError("用户名不存在");
            } else if (!password.equals(VALID_PASSWORD)) {
                listener.onError("密码错误");
            } else {
                listener.onSuccess(username);
            }
        }, 1500);
    }

    public interface OnLoginFinishedListener {
        void onSuccess(String username);
        void onError(String message);
    }
}

Presenter层(LoginPresenter.java)

java 复制代码
// LoginPresenter.java
package com.example.mvplogin.presenter;

import com.example.mvplogin.contract.LoginContract;
import com.example.mvplogin.model.LoginModel;

public class LoginPresenter implements LoginContract.Presenter, LoginModel.OnLoginFinishedListener {

    private LoginContract.View view;
    private final LoginModel model;

    public LoginPresenter() {
        this.model = new LoginModel();
    }

    @Override
    public void attachView(LoginContract.View view) {
        this.view = view;
    }

    @Override
    public void detachView() {
        this.view = null;
    }

    @Override
    public void login() {
        if (view == null) return;
        
        String username = view.getUsername();
        String password = view.getPassword();
        
        view.showLoading();
        model.login(username, password, this);
    }

    @Override
    public void onSuccess(String username) {
        if (view != null) {
            view.hideLoading();
            view.showLoginSuccess(username);
        }
    }

    @Override
    public void onError(String message) {
        if (view != null) {
            view.hideLoading();
            view.showLoginError(message);
        }
    }
}

2.3 View层实现

最后,我们在View层(如Activity或Fragment)中实现LoginContract接口,并将操作委托给Presenter。

视图层 (LoginActivity.java):

java 复制代码
// LoginActivity.java
package com.example.mvplogin.view;

import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;

import com.example.mvplogin.R;
import com.example.mvplogin.contract.LoginContract;
import com.example.mvplogin.presenter.LoginPresenter;

public class LoginActivity extends AppCompatActivity implements LoginContract.View {

    private EditText etUsername, etPassword;
    private Button btnLogin;
    private ProgressBar progressBar;
    
    private LoginContract.Presenter presenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_login);
        
        // 初始化Presenter
        presenter = new LoginPresenter();
        presenter.attachView(this);
        
        // 初始化UI组件
        etUsername = findViewById(R.id.etUsername);
        etPassword = findViewById(R.id.etPassword);
        btnLogin = findViewById(R.id.btnLogin);
        progressBar = findViewById(R.id.progressBar);
        
        // 设置登录按钮点击事件
        btnLogin.setOnClickListener(v -> presenter.login());
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.detachView();
    }

    @Override
    public void showLoading() {
        progressBar.setVisibility(View.VISIBLE);
        btnLogin.setEnabled(false);
    }

    @Override
    public void hideLoading() {
        progressBar.setVisibility(View.GONE);
        btnLogin.setEnabled(true);
    }

    @Override
    public void showLoginSuccess(String username) {
        // 跳转到欢迎页面
        Intent intent = new Intent(this, WelcomeActivity.class);
        intent.putExtra("username", username);
        startActivity(intent);
        finish();
    }

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

    @Override
    public String getUsername() {
        return etUsername.getText().toString().trim();
    }

    @Override
    public String getPassword() {
        return etPassword.getText().toString().trim();
    }
}

欢迎页面 (WelcomeActivity.java):

java 复制代码
// WelcomeActivity.java

package com.example.mvplogin.view;

import android.os.Bundle;
import android.widget.TextView;

import androidx.appcompat.app.AppCompatActivity;

import com.example.mvplogin.R;

public class WelcomeActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_welcome);
        
        TextView tvWelcome = findViewById(R.id.tvWelcome);
        String username = getIntent().getStringExtra("username");
        tvWelcome.setText("欢迎," + username + "!");
    }
}

2.4 相关布局文件

布局文件 (activity_login.xml):

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="32dp"
    android:gravity="center"
    tools:context=".view.LoginActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="登录"
        android:textSize="24sp"
        android:textStyle="bold"
        android:layout_marginBottom="32dp"/>

    <EditText
        android:id="@+id/etUsername"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="用户名"
        android:inputType="text"
        android:layout_marginBottom="16dp"/>

    <EditText
        android:id="@+id/etPassword"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="密码"
        android:inputType="textPassword"
        android:layout_marginBottom="24dp"/>

    <Button
        android:id="@+id/btnLogin"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="登录"
        android:layout_marginBottom="16dp"/>

    <ProgressBar
        android:id="@+id/progressBar"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:layout_gravity="center"/>

</LinearLayout>

欢迎页面布局 (activity_welcome.xml):

xml 复制代码
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    android:padding="32dp"
    tools:context=".view.WelcomeActivity">

    <TextView
        android:id="@+id/tvWelcome"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="欢迎"
        android:textSize="24sp"
        android:textStyle="bold"/>

</LinearLayout>

字符串资源 (strings.xml):

xml 复制代码
<resources>
    <string name="app_name">MVP登录示例</string>
</resources>
布局说明:
  • 用户名输入框 (EditText):用户输入登录用户名的地方,id为etUsername。
  • 密码输入框 (EditText):用户输入登录密码的地方,id为etPassword,并设置了输入类型为密码格式。
  • 登录按钮 (Button):用于触发登录操作的按钮,id为btnLogin。
  • 加载进度条 (ProgressBar):在处理登录请求时显示的加载指示器,默认隐藏(visibility="gone"),只有在请求处理中显示。

🌭3. 结论

MVP架构通过将View、Model、Presenter三层彻底解耦,使得业务逻辑、UI展示、数据处理分别在不同的层中负责。这样不仅提高了代码的可维护性和可测试性,也使得项目的扩展性更强。在实际开发中,MVP非常适用于复杂度较高的应用程序,尤其是在需要严格分离UI逻辑和业务逻辑的场景下

相关推荐
代码会说话3 小时前
i2c通讯
android·linux·嵌入式硬件·嵌入式
数据智能老司机4 小时前
Snowflake Cortex AI:面向生成式 AI 应用的解决方案——机器学习函数概览
大数据·架构·数据分析
默|笙5 小时前
【c++】set和map的封装
android·数据库·c++
数据智能老司机6 小时前
Snowflake Cortex AI:面向生成式 AI 应用的解决方案——理解 Snowflake Cortex
大数据·架构·数据分析
kaikaile19956 小时前
PHP计算过去一定时间段内日期范围函数
android·开发语言·php
数据智能老司机6 小时前
Snowflake Cortex AI:面向生成式 AI 应用的解决方案——Snowflake 生态中的 AI/ML 入门
大数据·架构·数据分析
2501_929382656 小时前
电视盒子助手开心电视助手 v8.0 删除电视内置软件 电视远程控制ADB去除电视广告
android·windows·adb·开源软件·电视盒子
稻草人22226 小时前
打造个人Dify?手写AI工作流让AI乖乖听话 - 基础架构篇
架构
太过平凡的小蚂蚁7 小时前
Kotlin 异步数据流三剑客:Flow、Channel、StateFlow 深度解析
android·kotlin