16 KiB
16 KiB
MVP架构
目录
MVP模式介绍
MVP 概念
MVP(Model-View-Presenter)是一种软件架构模式,是 MVC 的改进版本:
- Model:数据和业务逻辑
- View:用户界面(通过接口与 Presenter 通信)
- Presenter:处理业务逻辑,协调 Model 和 View
MVP 架构图
┌─────────┐
│ View │ ←─── 显示数据(通过接口)
└────┬────┘
│ 用户交互
↓
┌─────────┐
│Presenter │ ←─── 处理业务逻辑
└────┬────┘
│ 更新
↓
┌─────────┐
│ Model │ ←─── 数据和业务逻辑
└─────────┘
MVP 数据流
- 用户操作 View → View 调用 Presenter 方法
- Presenter 处理 → 调用 Model 获取数据
- Model 返回数据 → Presenter 处理数据
- Presenter 更新 View → 通过接口更新 View
MVP 核心特点
- View 和 Model 完全解耦:View 不直接访问 Model
- View 通过接口与 Presenter 通信:便于测试和替换
- Presenter 持有 View 接口引用:不持有具体 View 实现
- 业务逻辑在 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优缺点
优点
- View 和 Model 完全解耦:View 不直接访问 Model
- 易于测试:Presenter 可以独立测试,使用 Mock View
- 代码复用:Presenter 可以被多个 View 使用
- 职责清晰:每层职责明确
- 便于维护:代码结构清晰,易于维护
缺点
- 代码量增加:需要定义接口,代码量增加
- 接口过多:每个 View 都需要定义接口
- Presenter 可能臃肿:复杂业务逻辑可能导致 Presenter 臃肿
- 生命周期管理:需要手动管理 Presenter 的生命周期
MVP与MVC对比
主要区别
| 特性 | MVC | MVP |
|---|---|---|
| View 和 Model | 耦合 | 完全解耦 |
| View 访问 Model | 可以直接访问 | 不能直接访问 |
| Controller/Presenter | 处理输入和更新 | 处理业务逻辑 |
| 测试性 | 难以测试 | 易于测试 |
| 代码量 | 较少 | 较多 |
| 适用场景 | 小型项目 | 中大型项目 |
代码对比
// MVC:View 直接访问 Model
public class MainActivity extends AppCompatActivity {
private UserRepository repository;
private void loadUsers() {
repository.getUsers(users -> {
// View 直接处理 Model 数据
adapter.updateUsers(users);
});
}
}
// MVP:View 通过 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 模式?
答案: MVP(Model-View-Presenter)是一种软件架构模式:
- Model:数据和业务逻辑
- View:用户界面(通过接口与 Presenter 通信)
- Presenter:处理业务逻辑,协调 Model 和 View
Q2: MVP 和 MVC 的区别?
答案:
- MVC:View 可以直接访问 Model,Controller 处理输入和更新
- MVP:View 和 Model 完全解耦,View 通过接口与 Presenter 通信,Presenter 处理业务逻辑
Q3: MVP 的优点?
答案:
- View 和 Model 完全解耦
- 易于测试(Presenter 可以独立测试)
- 代码复用(Presenter 可以被多个 View 使用)
- 职责清晰
- 便于维护
Q4: MVP 的缺点?
答案:
- 代码量增加(需要定义接口)
- 接口过多
- Presenter 可能臃肿
- 需要手动管理生命周期
Q5: 如何防止 Presenter 内存泄漏?
答案:
- 在 onDestroy 中调用
presenter.detachView() - Presenter 持有 View 接口的弱引用
- 使用
isViewAttached()检查 View 是否可用 - 及时取消网络请求和订阅
Q6: MVP 中如何测试 Presenter?
答案:
- 创建 Mock View 实现 View 接口
- 创建 Mock Model/Repository
- 测试 Presenter 的业务逻辑
- 验证 Presenter 是否正确调用 View 方法
总结
MVP 是 Android 开发中常用的架构模式,通过 View 接口实现 View 和 Model 的解耦,使代码更易于测试和维护。虽然代码量会增加,但对于中大型项目来说,这种结构化的方式更有利于长期维护。
最后更新:2024年