故事背景:智能餐厅的点餐系统
想象一下,我们开了一家"代码美味"餐厅,餐厅里有三个角色:
- 顾客 (View) :只会说"我要点餐"和看最终上来的菜品
- 服务员 (Presenter) :负责接收订单、协调后厨、把菜品端给顾客
- 厨师 (Model) :在后厨默默做饭,顾客看不到他
角色分工代码实现
1. 厨师 (Model) - 数据和处理逻辑
java
// 厨师 - 负责真正的烹饪工作
public class Chef {
public interface OnCookingListener {
void onSuccess(String food);
void onError(String message);
}
// 真正的业务逻辑在这里
public void cookFood(String order, OnCookingListener listener) {
new Thread(() -> {
try {
// 模拟烹饪时间
Thread.sleep(2000);
switch (order) {
case "宫保鸡丁":
listener.onSuccess("香喷喷的宫保鸡丁做好了!");
break;
case "麻婆豆腐":
listener.onSuccess("热辣辣的麻婆豆腐出锅了!");
break;
default:
listener.onError("抱歉,这道菜我们不会做");
}
} catch (InterruptedException e) {
listener.onError("厨房出问题了!");
}
}).start();
}
}
2. 服务员 (Presenter) - 业务协调员
java
// 服务员 - 连接顾客和厨师的中介
public class Waiter {
private CustomerView customerView;
private Chef chef;
public Waiter(CustomerView view) {
this.customerView = view;
this.chef = new Chef();
}
// 接收顾客订单,协调后厨工作
public void takeOrder(String foodName) {
// 先告诉顾客:订单已收到
customerView.showLoading("正在为您准备 " + foodName + "...");
// 然后把订单交给厨师
chef.cookFood(foodName, new Chef.OnCookingListener() {
@Override
public void onSuccess(String result) {
// 厨师做好了,服务员把菜品端给顾客
customerView.hideLoading();
customerView.showFood(result);
}
@Override
public void onError(String message) {
// 厨师说做不了,服务员告诉顾客
customerView.hideLoading();
customerView.showError(message);
}
});
}
// 清理工作
public void onDestroy() {
customerView = null;
}
}
3. 顾客 (View) - 界面展示
java
// 顾客接口 - 定义顾客能做什么
public interface CustomerView {
void showLoading(String message); // 显示等待
void hideLoading(); // 隐藏等待
void showFood(String food); // 展示菜品
void showError(String message); // 展示错误
}
// 具体的顾客 - Activity实现
public class RestaurantActivity extends AppCompatActivity implements CustomerView {
private TextView statusText;
private Button orderButton1, orderButton2;
private ProgressBar progressBar;
private Waiter waiter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_restaurant);
// 初始化界面
statusText = findViewById(R.id.status_text);
orderButton1 = findViewById(R.id.btn_order1);
orderButton2 = findViewById(R.id.btn_order2);
progressBar = findViewById(R.id.progress_bar);
// 请来服务员
waiter = new Waiter(this);
// 设置点餐按钮
orderButton1.setOnClickListener(v -> waiter.takeOrder("宫保鸡丁"));
orderButton2.setOnClickListener(v -> waiter.takeOrder("麻婆豆腐"));
}
// 实现顾客接口的方法
@Override
public void showLoading(String message) {
runOnUiThread(() -> {
progressBar.setVisibility(View.VISIBLE);
statusText.setText(message);
});
}
@Override
public void hideLoading() {
runOnUiThread(() -> {
progressBar.setVisibility(View.GONE);
});
}
@Override
public void showFood(String food) {
runOnUiThread(() -> {
statusText.setText(food);
Toast.makeText(this, "用餐愉快!", Toast.LENGTH_SHORT).show();
});
}
@Override
public void showError(String message) {
runOnUiThread(() -> {
statusText.setText(message);
Toast.makeText(this, "出错了!", Toast.LENGTH_SHORT).show();
});
}
@Override
protected void onDestroy() {
super.onDestroy();
// 餐厅打烊,服务员下班
waiter.onDestroy();
}
}
MVP调用时序图

错误情况的时序图

MVP的核心优势
1. 分工明确,各司其职
- Model: 只关心数据和业务逻辑(厨师只管做饭)
- View: 只关心界面显示(顾客只管看和点餐)
- Presenter: 协调两者(服务员传递信息)
2. 易于测试
java
// 可以单独测试Presenter,不需要Android环境
@Test
public void testPresenter() {
// 创建模拟的View
CustomerView mockView = mock(CustomerView.class);
Waiter waiter = new Waiter(mockView);
// 测试点餐逻辑
waiter.takeOrder("宫保鸡丁");
// 验证View被正确调用
verify(mockView).showLoading(anyString());
}
3. 代码可维护性高
当需要修改界面时,只需要改动View;
当需要修改业务逻辑时,只需要改动Model;
它们互不影响!
总结
通过这个餐厅的故事,我们可以看到MVP架构就像一家组织良好的餐厅:
- 顾客 (View) 不需要知道菜品怎么做,只需要表达需求看结果
- 服务员 (Presenter) 不亲自做饭,但协调整个流程
- 厨师 (Model) 专心做饭,不直接面对顾客
这种架构让我们的代码更加清晰、可维护、可测试,就像一家运转良好的餐厅一样高效有序!