Files
mkdocs/docs/android面试/系统架构/MVP架构.md
2026-01-15 11:53:37 +08:00

16 KiB
Raw Permalink Blame History

MVP架构

目录


MVP模式介绍

MVP 概念

MVPModel-View-Presenter是一种软件架构模式是 MVC 的改进版本:

  • Model:数据和业务逻辑
  • View:用户界面(通过接口与 Presenter 通信)
  • Presenter:处理业务逻辑,协调 Model 和 View

MVP 架构图

┌─────────┐
│  View   │  ←─── 显示数据(通过接口)
└────┬────┘
     │ 用户交互
     ↓
┌─────────┐
│Presenter │  ←─── 处理业务逻辑
└────┬────┘
     │ 更新
     ↓
┌─────────┐
│  Model  │  ←─── 数据和业务逻辑
└─────────┘

MVP 数据流

  1. 用户操作 View → View 调用 Presenter 方法
  2. Presenter 处理 → 调用 Model 获取数据
  3. Model 返回数据 → Presenter 处理数据
  4. Presenter 更新 View → 通过接口更新 View

MVP 核心特点

  1. View 和 Model 完全解耦View 不直接访问 Model
  2. View 通过接口与 Presenter 通信:便于测试和替换
  3. Presenter 持有 View 接口引用:不持有具体 View 实现
  4. 业务逻辑在 Presenter 中View 只负责显示

MVP三层职责

Model 层

职责:

  • 数据模型定义
  • 业务逻辑处理
  • 数据访问(网络、数据库、文件等)
  • 数据验证

特点:

  • 不依赖 View 和 Presenter
  • 可以独立测试
  • 可以被多个 Presenter 使用

View 层

职责:

  • 显示数据
  • 处理用户输入
  • 提供 UI 反馈(加载、错误提示等)

特点:

  • 通过接口与 Presenter 通信
  • 不包含业务逻辑
  • 被动接收 Presenter 的更新

Presenter 层

职责:

  • 处理用户交互
  • 调用 Model 获取数据
  • 处理业务逻辑
  • 更新 View通过接口

特点:

  • 持有 View 接口引用
  • 不持有具体 View 实现
  • 可以独立测试(使用 Mock View

MVP实现示例

基础 MVP 实现

// Model用户数据模型
public class User {
    private String id;
    private String name;
    private String email;
    
    public User(String id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }
    
    // Getters
    public String getId() { return id; }
    public String getName() { return name; }
    public String getEmail() { return email; }
}

// Model用户业务逻辑
public class UserRepository {
    private List<User> users = new ArrayList<>();
    
    public void addUser(User user, Callback callback) {
        // 模拟网络请求
        new Thread(() -> {
            try {
                Thread.sleep(1000); // 模拟网络延迟
                users.add(user);
                callback.onSuccess();
            } catch (InterruptedException e) {
                callback.onError(e.getMessage());
            }
        }).start();
    }
    
    public void getUsers(Callback<List<User>> callback) {
        new Thread(() -> {
            try {
                Thread.sleep(500);
                callback.onSuccess(users);
            } catch (InterruptedException e) {
                callback.onError(e.getMessage());
            }
        }).start();
    }
    
    interface Callback<T> {
        void onSuccess(T data);
        void onError(String error);
    }
}

// View 接口
public interface UserView {
    void showLoading();
    void hideLoading();
    void showUsers(List<User> users);
    void showError(String error);
    void showSuccess(String message);
    void clearInput();
}

// Presenter
public class UserPresenter {
    private UserView view;
    private UserRepository repository;
    
    public UserPresenter(UserView view, UserRepository repository) {
        this.view = view;
        this.repository = repository;
    }
    
    public void addUser(String name, String email) {
        if (name.isEmpty() || email.isEmpty()) {
            view.showError("Name and Email cannot be empty");
            return;
        }
        
        view.showLoading();
        User user = new User(UUID.randomUUID().toString(), name, email);
        repository.addUser(user, new UserRepository.Callback<Void>() {
            @Override
            public void onSuccess(Void data) {
                view.hideLoading();
                view.showSuccess("User added successfully");
                view.clearInput();
                loadUsers();
            }
            
            @Override
            public void onError(String error) {
                view.hideLoading();
                view.showError(error);
            }
        });
    }
    
    public void loadUsers() {
        view.showLoading();
        repository.getUsers(new UserRepository.Callback<List<User>>() {
            @Override
            public void onSuccess(List<User> data) {
                view.hideLoading();
                view.showUsers(data);
            }
            
            @Override
            public void onError(String error) {
                view.hideLoading();
                view.showError(error);
            }
        });
    }
    
    public void onDestroy() {
        view = null; // 防止内存泄漏
    }
}

// View 实现Activity
public class UserActivity extends AppCompatActivity implements UserView {
    private EditText etName, etEmail;
    private Button btnAdd;
    private RecyclerView rvUsers;
    private UserPresenter presenter;
    private UserAdapter adapter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user);
        
        initViews();
        initPresenter();
        setupRecyclerView();
        setupListeners();
        
        presenter.loadUsers();
    }
    
    private void initViews() {
        etName = findViewById(R.id.etName);
        etEmail = findViewById(R.id.etEmail);
        btnAdd = findViewById(R.id.btnAdd);
        rvUsers = findViewById(R.id.rvUsers);
    }
    
    private void initPresenter() {
        UserRepository repository = new UserRepository();
        presenter = new UserPresenter(this, repository);
    }
    
    private void setupRecyclerView() {
        adapter = new UserAdapter(new ArrayList<>());
        rvUsers.setLayoutManager(new LinearLayoutManager(this));
        rvUsers.setAdapter(adapter);
    }
    
    private void setupListeners() {
        btnAdd.setOnClickListener(v -> {
            String name = etName.getText().toString();
            String email = etEmail.getText().toString();
            presenter.addUser(name, email);
        });
    }
    
    // 实现 UserView 接口
    @Override
    public void showLoading() {
        // 显示加载提示
    }
    
    @Override
    public void hideLoading() {
        // 隐藏加载提示
    }
    
    @Override
    public void showUsers(List<User> users) {
        adapter.updateUsers(users);
    }
    
    @Override
    public void showError(String error) {
        Toast.makeText(this, error, Toast.LENGTH_SHORT).show();
    }
    
    @Override
    public void showSuccess(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }
    
    @Override
    public void clearInput() {
        etName.setText("");
        etEmail.setText("");
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (presenter != null) {
            presenter.onDestroy();
        }
    }
}

使用 RxJava 的 MVP 实现

// Presenter with RxJava
public class UserPresenter {
    private UserView view;
    private UserRepository repository;
    private CompositeDisposable disposables = new CompositeDisposable();
    
    public UserPresenter(UserView view, UserRepository repository) {
        this.view = view;
        this.repository = repository;
    }
    
    public void addUser(String name, String email) {
        if (name.isEmpty() || email.isEmpty()) {
            view.showError("Name and Email cannot be empty");
            return;
        }
        
        view.showLoading();
        User user = new User(UUID.randomUUID().toString(), name, email);
        
        Disposable disposable = repository.addUser(user)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                () -> {
                    view.hideLoading();
                    view.showSuccess("User added successfully");
                    view.clearInput();
                    loadUsers();
                },
                error -> {
                    view.hideLoading();
                    view.showError(error.getMessage());
                }
            );
        
        disposables.add(disposable);
    }
    
    public void loadUsers() {
        view.showLoading();
        Disposable disposable = repository.getUsers()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                users -> {
                    view.hideLoading();
                    view.showUsers(users);
                },
                error -> {
                    view.hideLoading();
                    view.showError(error.getMessage());
                }
            );
        
        disposables.add(disposable);
    }
    
    public void onDestroy() {
        disposables.clear();
        view = null;
    }
}

MVP优缺点

优点

  1. View 和 Model 完全解耦View 不直接访问 Model
  2. 易于测试Presenter 可以独立测试,使用 Mock View
  3. 代码复用Presenter 可以被多个 View 使用
  4. 职责清晰:每层职责明确
  5. 便于维护:代码结构清晰,易于维护

缺点

  1. 代码量增加:需要定义接口,代码量增加
  2. 接口过多:每个 View 都需要定义接口
  3. Presenter 可能臃肿:复杂业务逻辑可能导致 Presenter 臃肿
  4. 生命周期管理:需要手动管理 Presenter 的生命周期

MVP与MVC对比

主要区别

特性 MVC MVP
View 和 Model 耦合 完全解耦
View 访问 Model 可以直接访问 不能直接访问
Controller/Presenter 处理输入和更新 处理业务逻辑
测试性 难以测试 易于测试
代码量 较少 较多
适用场景 小型项目 中大型项目

代码对比

// MVCView 直接访问 Model
public class MainActivity extends AppCompatActivity {
    private UserRepository repository;
    
    private void loadUsers() {
        repository.getUsers(users -> {
            // View 直接处理 Model 数据
            adapter.updateUsers(users);
        });
    }
}

// MVPView 通过 Presenter 访问 Model
public class MainActivity extends AppCompatActivity implements UserView {
    private UserPresenter presenter;
    
    private void loadUsers() {
        presenter.loadUsers(); // 通过 Presenter
    }
    
    @Override
    public void showUsers(List<User> users) {
        // Presenter 通过接口更新 View
        adapter.updateUsers(users);
    }
}

MVP最佳实践

1. View 接口设计

// ✅ 好的接口设计:职责单一
public interface UserView {
    void showUsers(List<User> users);
    void showError(String error);
    void showLoading();
    void hideLoading();
}

// ❌ 不好的接口设计:职责过多
public interface UserView {
    void showUsers(List<User> users);
    void showError(String error);
    void showLoading();
    void hideLoading();
    void navigateToDetail(); // 导航不应该在 View 接口中
    void requestPermission(); // 权限不应该在 View 接口中
}

2. Presenter 生命周期管理

public class UserPresenter {
    private UserView view;
    
    public void attachView(UserView view) {
        this.view = view;
    }
    
    public void detachView() {
        this.view = null;
    }
    
    public boolean isViewAttached() {
        return view != null;
    }
    
    private void updateView(Runnable update) {
        if (isViewAttached()) {
            update.run();
        }
    }
}

// Activity 中使用
public class UserActivity extends AppCompatActivity implements UserView {
    private UserPresenter presenter;
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        presenter = new UserPresenter();
        presenter.attachView(this);
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        presenter.detachView();
    }
}

3. 使用 Contract 接口

// 将 View 和 Presenter 接口放在一起
public interface UserContract {
    interface View {
        void showUsers(List<User> users);
        void showError(String error);
        void showLoading();
        void hideLoading();
    }
    
    interface Presenter {
        void loadUsers();
        void addUser(String name, String email);
        void attachView(View view);
        void detachView();
    }
}

// 实现
public class UserPresenter implements UserContract.Presenter {
    private UserContract.View view;
    // ...
}

public class UserActivity extends AppCompatActivity implements UserContract.View {
    private UserContract.Presenter presenter;
    // ...
}

4. 使用 BasePresenter 和 BaseView

// BaseView
public interface BaseView {
    void showLoading();
    void hideLoading();
    void showError(String error);
}

// BasePresenter
public class BasePresenter<V extends BaseView> {
    protected V view;
    
    public void attachView(V view) {
        this.view = view;
    }
    
    public void detachView() {
        this.view = null;
    }
    
    public boolean isViewAttached() {
        return view != null;
    }
}

// 使用
public interface UserView extends BaseView {
    void showUsers(List<User> users);
}

public class UserPresenter extends BasePresenter<UserView> {
    // ...
}

面试常见问题

Q1: 什么是 MVP 模式?

答案: MVPModel-View-Presenter是一种软件架构模式

  • Model:数据和业务逻辑
  • View:用户界面(通过接口与 Presenter 通信)
  • Presenter:处理业务逻辑,协调 Model 和 View

Q2: MVP 和 MVC 的区别?

答案:

  • MVCView 可以直接访问 ModelController 处理输入和更新
  • MVPView 和 Model 完全解耦View 通过接口与 Presenter 通信Presenter 处理业务逻辑

Q3: MVP 的优点?

答案:

  1. View 和 Model 完全解耦
  2. 易于测试Presenter 可以独立测试)
  3. 代码复用Presenter 可以被多个 View 使用)
  4. 职责清晰
  5. 便于维护

Q4: MVP 的缺点?

答案:

  1. 代码量增加(需要定义接口)
  2. 接口过多
  3. Presenter 可能臃肿
  4. 需要手动管理生命周期

Q5: 如何防止 Presenter 内存泄漏?

答案:

  1. 在 onDestroy 中调用 presenter.detachView()
  2. Presenter 持有 View 接口的弱引用
  3. 使用 isViewAttached() 检查 View 是否可用
  4. 及时取消网络请求和订阅

Q6: MVP 中如何测试 Presenter

答案:

  1. 创建 Mock View 实现 View 接口
  2. 创建 Mock Model/Repository
  3. 测试 Presenter 的业务逻辑
  4. 验证 Presenter 是否正确调用 View 方法

总结

MVP 是 Android 开发中常用的架构模式,通过 View 接口实现 View 和 Model 的解耦,使代码更易于测试和维护。虽然代码量会增加,但对于中大型项目来说,这种结构化的方式更有利于长期维护。


最后更新2024年