把 VIPER 的 5 个组件类比成餐厅里的岗位,一下子就能把 “各司其职” 的核心逻辑讲透。咱们就从 “你去餐厅点一份番茄炒蛋” 这个场景入手,一步步拆透 VIPER 的实现原理。
一、先搞懂:VIPER = 餐厅里的 5 个核心角色
VIPER 是5 个组件的分工协作模式,每个组件只干自己的活,就像餐厅里没人既当服务员又当厨师。先记住这 5 个 “岗位” 对应的职责:
| VIPER 组件 | 餐厅角色 | 核心职责(Android 场景) |
|---|---|---|
| View | 服务员 | 只和用户互动:显示界面、接收点击(比如 “点单按钮”),不做任何逻辑处理 |
| Presenter | 领班 | 中间调度员:接收 View 的请求,转发给 Interactor,再把结果回传给 View |
| Interactor | 后厨厨师 | 干脏活累活:处理业务逻辑(比如 “查番茄炒蛋的菜谱”“算价格”)、操作数据 |
| Entity | 食材 / 菜品 | 纯数据模型:只存数据(比如番茄炒蛋的名字、食材、价格),没有任何逻辑 |
| Router | 门童 / 引路员 | 负责跳转:处理页面间的导航(比如 “从点餐页跳转到菜品详情页”) |
二、实战:用 “点番茄炒蛋” 拆透调用流程 + 代码
咱们以 “用户在 APP 点餐页点击‘点番茄炒蛋’,最终显示菜品详情” 为例,一步步看每个组件怎么配合。
1. 第一步:定义 “食材”——Entity(纯数据类)
Entity 是最单纯的,只存数据,像餐厅里的 “番茄炒蛋” 这个菜品本身,只包含名字、食材、价格这些属性。
// Entity:纯数据模型,无任何逻辑
data class DishEntity(
val name: String, // 菜名:番茄炒蛋
val ingredients: String,// 食材:番茄2个+鸡蛋3个
val price: Float // 价格:28元
)
2. 第二步:服务员待命 ——View(Activity/Fragment)
View 就像服务员,只做两件事:显示界面和把用户的需求传给领班(Presenter) 。它不知道 “番茄炒蛋怎么做”,也不知道 “价格怎么算”。
// View:只负责UI交互,不做逻辑
class OrderActivity : AppCompatActivity(), OrderContract.View {
// View持有Presenter的引用(服务员找得到领班)
private lateinit var presenter: OrderContract.Presenter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_order)
// 初始化Presenter(服务员上岗前找到领班)
presenter = OrderPresenter(this)
// 1. 用户点击“点番茄炒蛋”按钮(接收用户操作)
btn_order_egg_tomato.setOnClickListener {
// 2. 把需求传给Presenter,自己啥也不干
presenter.handleOrderDish("番茄炒蛋")
}
}
// 4. 接收Presenter的结果,更新UI(领班让服务员给用户看菜单)
override fun showDishDetail(dish: DishEntity) {
tv_dish_name.text = dish.name
tv_dish_ingredients.text = "食材:${dish.ingredients}"
tv_dish_price.text = "价格:${dish.price}元"
}
// 跳转页面(由Router处理,View只负责触发)
override fun jumpToDetailPage(dish: DishEntity) {
OrderRouter.jumpToDetail(this, dish)
}
}
// (关键)View和Presenter的协议:定义View能做的事(避免直接依赖)
interface OrderContract.View {
fun showDishDetail(dish: DishEntity) // 显示菜品详情
fun jumpToDetailPage(dish: DishEntity) // 跳转到详情页
}
3. 第三步:领班调度 ——Presenter(中间协调者)
Presenter 是 “总指挥”,它既懂 View 的需求,也知道该找哪个 Interactor 干活。它不自己做具体逻辑,只负责 “转发” 和 “回调”。
// Presenter:中间调度,不做具体逻辑
class OrderPresenter(
private val view: OrderContract.View // 持有View引用,方便回调
) : OrderContract.Presenter, DishInteractor.Callback {
// Presenter持有Interactor引用(领班找得到后厨)
private val dishInteractor = DishInteractor()
// 2. 接收View的请求(服务员告诉领班:用户要点番茄炒蛋)
override fun handleOrderDish(dishName: String) {
// 3. 转发给Interactor,让后厨查这个菜的信息
dishInteractor.getDishInfo(dishName, this)
}
// 5. 接收Interactor的结果(后厨把菜谱给领班)
override fun onDishFound(dish: DishEntity) {
// 6. 把结果回传给View,让服务员展示给用户
view.showDishDetail(dish)
// 同时让Router跳转详情页(领班让门童引路)
view.jumpToDetailPage(dish)
}
// 处理“菜没找到”的情况
override fun onDishNotFound() {
view.showError("不好意思,这个菜卖完了~")
}
}
// Presenter的协议:定义Presenter能处理的请求
interface OrderContract.Presenter {
fun handleOrderDish(dishName: String) // 处理点单请求
}
4. 第四步:后厨干活 ——Interactor(业务逻辑处理)
Interactor 是 “打工人”,负责所有脏活累活:查数据、算价格、调用接口等。它不知道 View 的存在,只通过回调把结果告诉 Presenter。
// Interactor:处理业务逻辑,操作数据
class DishInteractor {
// 3. 接收Presenter的请求(领班让后厨查番茄炒蛋的信息)
fun getDishInfo(dishName: String, callback: Callback) {
// 模拟“从服务器/数据库查数据”的耗时操作(后厨备菜需要时间)
Thread {
Thread.sleep(1000) // 模拟网络延迟
// 业务逻辑:判断是不是有这个菜
if (dishName == "番茄炒蛋") {
// 4. 查到数据,通过回调告诉Presenter(后厨把菜给领班)
val tomatoEgg = DishEntity(
name = "番茄炒蛋",
ingredients = "番茄2个+鸡蛋3个+盐1勺",
price = 28.0f
)
callback.onDishFound(tomatoEgg)
} else {
callback.onDishNotFound()
}
}.start()
}
// Interactor和Presenter的回调协议:定义结果怎么传
interface Callback {
fun onDishFound(dish: DishEntity) // 找到菜品
fun onDishNotFound() // 没找到菜品
}
}
5. 第五步:引路 ——Router(页面跳转)
Router 只负责 “跳转”,像门童一样,知道从哪个页面到哪个页面该走哪条路。它不处理任何业务逻辑。
// Router:只负责页面跳转
object OrderRouter {
// 7. 接收View的跳转请求(门童接到引路需求)
fun jumpToDetail(context: Context, dish: DishEntity) {
// 跳转到详情页,并传递数据
val intent = Intent(context, DishDetailActivity::class.java)
intent.putExtra("DISH_INFO", dish) // 传递DishEntity(需实现Parcelable)
context.startActivity(intent)
}
}
// 注意:DishEntity需要实现Parcelable才能传递,修改一下Entity:
data class DishEntity(
val name: String,
val ingredients: String,
val price: Float
) : Parcelable { // 新增Parcelable实现,用于页面间传值
// (自动生成的Parcelable代码,Android Studio可自动生成)
constructor(parcel: Parcel) : this(
parcel.readString() ?: "",
parcel.readString() ?: "",
parcel.readFloat()
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeString(ingredients)
parcel.writeFloat(price)
}
override fun describeContents(): Int = 0
companion object CREATOR : Parcelable.Creator<DishEntity> {
override fun createFromParcel(parcel: Parcel): DishEntity = DishEntity(parcel)
override fun newArray(size: Int): Array<DishEntity?> = arrayOfNulls(size)
}
}
三、时序图:一眼看清整个调用流程
用 mermaid 时序图把上面的 7 步串起来,谁先谁后一目了然:
Router(OrderRouter)Entity(DishEntity)Interactor(DishInteractor)Presenter(OrderPresenter)View(OrderActivity)Router(OrderRouter)Entity(DishEntity)Interactor(DishInteractor)Presenter(OrderPresenter)View(OrderActivity)用户点击“点番茄炒蛋”按钮调用handleOrderDish("番茄炒蛋")调用getDishInfo("番茄炒蛋", 回调)模拟网络请求查菜品信息构造DishEntity(番茄炒蛋数据)调用onDishFound(dish)调用showDishDetail(dish)调用jumpToDetail(dish)跳转到DishDetailActivity
四、VIPER 的核心优势:为什么要这么麻烦?
可能你会问:“直接在 Activity 里查数据、更 UI 不更简单吗?” 这就像餐厅里 “服务员自己做菜”—— 短期简单,但人多了就乱套了。VIPER 的优势在于:
- 完全解耦:改 UI(换服务员)不影响业务逻辑(后厨),改逻辑不影响 UI;
- 易测试:比如想测试 “没找到菜” 的场景,直接调用 Interactor 的
onDishNotFound,不用点手机; - 易维护:出问题了能快速定位 ——UI 不显示找 View,逻辑错了找 Interactor,跳转错了找 Router。
不过缺点也很明显:
- 样板代码多 (Boilerplate Code) :需要创建很多类和接口,对于小功能来说显得臃肿。
- 学习曲线陡峭 (Steep Learning Curve) :对于新手来说,理解所有组件的职责和通信方式需要一些时间。