Android与MVC设计模式
理解 MVC 架构模式在 Android 中的映射关系,用 Kotlin data class 创建 Model,学会分离界面、数据和逻辑。
学习目标
- 能沿“Android与MVC设计模式”的用户事件解释Android组件、状态所有者、线程与销毁边界。
- 能围绕“理解 MVC 架构模式在 Android 中的映射关系,用 Kotlin data class 创建 Model,学会分离界面、数据和逻辑。”改出一个可运行结果,并用前后状态而非组件数量验收。
- 能在旋转、进程重建、拒权、离线或无效输入中选择适用反例,定位首个状态分叉。
- 能用构建指纹、用户操作、状态快照、原始日志和行为断言独立重放结论,并标明第四版机制与现代targetSdk政策的边界。
为什么代码全写在一起会爆炸
想象你开了一家面包店。做面包、接单、收银、打扫卫生全都你一个人干——刚开始一天 10 个顾客还行。但当顾客涨到 200 个时,你做面包的间隙要去收款,收完款发现烤箱里的面包已经焦了。这就是把所有代码塞进一个文件的真实处境。
↡Model-View-Controller,一种把软件拆成三层的架构模式。Model 管数据,View 管显示,Controller 管协调二者。好处是改界面不影响数据逻辑,改数据逻辑不影响界面。 设计模式就是帮你分工:把面包师傅(数据逻辑)、收银员和大家看到的面包橱窗(界面)分成三个独立的角色。每个角色只管自己一摊事,换一个橱窗的灯光装饰不用惊动面包师傅。
没有 MVC 会怎样?当你的应用从"一个按钮显示一句话"变成"三个界面、网络请求、数据库读写"时,所有代码纠缠在一个文件里——找一行代码要上下翻几百行,改一个颜色结果把登录逻辑也改坏了。这不是夸张,这是每个 Android 新手必然会踩进去的坑。MVC 就是帮你从一开始就把地盘划清楚。
概念讲解
MVC 三层:每层管什么
把应用想象成一个餐厅,MVC 就是餐厅里的三个角色:
| 角色 | MVC 层 | 职责 | Android 中对应 |
|---|---|---|---|
| 菜谱和大厨 | Model | 管数据——数据长什么样(类定义)、数据从哪来(网络/数据库)、数据怎么变(业务逻辑) | Kotlin data class、Repository 类 |
| 摆盘和餐桌 | View | 管显示——按钮放哪、文字什么颜色、列表怎么排 | res/layout/*.xml 布局文件 |
| 服务员 | Controller | 管协调——收到用户点击后找 Model 要数据,再把数据交给 View 显示 | Activity、Fragment |
三层不是各自孤立的,而是围成一个闭环:用户的每一次操作,都会沿着 View → Controller → Model → Controller → View 转一整圈。下面这张图把这一圈拆成六步,点播放看数据怎么流:
第 1 / 6 步 · ① 用户在 View 上操作(点了「正确」按钮)
点击播放,看一次「用户点一下」在 View / Controller / Model 三层之间流转一整圈;可暂停、单步、拖进度逐帧观察。
↡应用程序里负责管理数据和业务逻辑的那一层。它不知道数据最终会显示在什么界面上——只管数据的结构、来源和变化规则。 是最"干净"的一层——它不知道任何关于界面的事。一个 User 数据类不管自己最终是显示在登录页还是设置页,它只管定义"用户有 id、名字、头像 URL 这三个属性"。
↡应用程序里负责显示的那一层。在 Android 中特指 res/layout/ 下的 XML 布局文件。View 层只管「怎么显示」,不管「显示什么数据」——数据由 Controller 喂给它。 在 Android 里就是那些 XML 布局文件。它们定义了界面的骨架——有一个按钮、一个文本框、一个列表……但具体按钮上写什么字、列表里有哪些数据,View 不管,等着 Controller 来填。
↡MVC 中连接 Model 和 View 的协调者。根据用户操作从 Model 获取或更新数据,然后通知 View 刷新显示。在 Android 中,Activity 和 Fragment 承担 Controller 角色。 是最忙的角色——它不自己做面包也不自己摆盘,但知道"客人点了吐司,该叫面包师傅(Model)做什么"以及"做好了该放在哪个盘子(View)里端上去"。在 Android 里,Activity 和 Fragment 就是 Controller。
Kotlin data class:最轻量的 Model
Kotlin 里最适合做 Model 的就是 ↡Kotlin 的一个特殊类声明,编译器会自动为你生成 equals、hashCode、toString、copy 等方法。专门用来承载数据,本身不含业务逻辑。。定义一个最简单的"问题"模型:
data class Question(
val id: Int,
val text: String,
val answer: Boolean, // true = 正确, false = 错误
val isAnswered: Boolean = false
)三行代码,就得到了一个完整的 Model:它知道每个问题有一个编号、一段文字、一个正确答案和一个"是否已回答"的状态。val 表示这些属性创建后不可变——这是 Kotlin 推荐的做法:数据模型本身不自己改自己,修改由 Controller 操作。注意 isAnswered 有默认值 false,创建新问题时不用写这个参数。
Model 也可以是会"干活"的——不只是存数据。下面这个 Model 封装了一段判断逻辑:
data class QuizState(
val questions: List<Question>,
val currentIndex: Int = 0
) {
val currentQuestion: Question
get() = questions[currentIndex]
val isLastQuestion: Boolean
get() = currentIndex == questions.lastIndex
fun moveToNext(): QuizState =
copy(currentIndex = currentIndex + 1)
}copy() 是 data class 自带的方法——它创建一个新对象,只修改你指定的属性,其他属性保持不变。这个模型只管"当前到第几题了"和"下一题怎么走",完全不关心界面怎么显示——这才是干净的 Model。
一个反例:全写在 Activity 里是什么灾难
class MainActivity : AppCompatActivity() {
// 所有东西全塞在一起——
private var currentIndex = 0
private val questions = listOf("1+1=?", "2+2=?")
private val answers = listOf(true, false)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val textView = findViewById<TextView>(R.id.questionText)
val trueButton = findViewById<Button>(R.id.trueButton)
val falseButton = findViewById<Button>(R.id.falseButton)
textView.text = questions[currentIndex]
trueButton.setOnClickListener {
if (answers[currentIndex]) { /* 答对了 */ }
currentIndex++
if (currentIndex < questions.size) {
textView.text = questions[currentIndex]
}
}
// ... falseButton 也要写几乎一样的逻辑
}
}这段代码的问题是:① 数据和逻辑混在 Activity 里——考题数据、答题判断、翻页全在同一个类。② 加一个新功能(比如显示分数)要在这一个文件里改多处。③ 加第二个界面时,整段逻辑没法复用——你只能复制粘贴。
重构:拆出 QuizState Model
// Model — 纯粹的数据 + 逻辑,不碰 Android 任何 API
data class QuizState(
val questions: List<Question>,
val currentIndex: Int = 0,
val score: Int = 0
) {
val currentQuestion: Question get() = questions[currentIndex]
fun checkAnswer(userAnswer: Boolean): Boolean =
currentQuestion.answer == userAnswer
fun moveToNext(): QuizState =
copy(currentIndex = currentIndex + 1)
}// Controller — Activity 只管"界面事件→调Model→更新View"
class MainActivity : AppCompatActivity() {
private lateinit var quizState: QuizState
private lateinit var questionTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
questionTextView = findViewById(R.id.questionText)
quizState = QuizState(
questions = listOf(
Question(1, "Android 用 Kotlin 开发", true),
Question(2, "Android 用 Swift 开发", false)
)
)
updateQuestion()
}
private fun updateQuestion() {
questionTextView.text = quizState.currentQuestion.text
}
private fun onAnswer(userAnswer: Boolean) {
quizState.checkAnswer(userAnswer)
quizState = quizState.moveToNext()
updateQuestion()
}
}对比重构前后:Model 里没有任何 findViewById、setOnClickListener——它不依赖 Android SDK,可以在单元测试里直接测。Activity 里只剩"找到控件 → 调 Model → 更新控件"这几步,逻辑清晰到能一眼看懂。
动手:从零搭一个 MVC 答题应用
下面一步步搭一个遵守 MVC 的应用,每步都标注你在写哪一层。
猜一猜:MVC 三层中,哪一层的代码量应该最少?做完下面的练习再看答案。
① 创建 Model(Question + QuizState)
在 java/ 下新建 model/Question.kt:
package com.example.myapp.model
data class Question(
val id: Int,
val text: String,
val answer: Boolean
)再新建 model/QuizState.kt:
package com.example.myapp.model
data class QuizState(
val questions: List<Question>,
val currentIndex: Int = 0
) {
val currentQuestion: Question
get() = questions[currentIndex]
fun checkAnswer(userAnswer: Boolean): Boolean =
currentQuestion.answer == userAnswer
fun moveToNext(): QuizState =
copy(currentIndex = currentIndex + 1)
}这一步只建了数据结构和规则——不依赖任何 Android API。
小结
- MVC 把应用拆为三层:Model(数据+逻辑)、View(布局XML)、Controller(Activity)——各管一层,互不越界
- Kotlin
data class是 Model 的最佳载体——copy()创建不可变对象,业务逻辑封装在 Model 内部 - Controller 是协调者:事件来了→调 Model→拿结果→更新 View,本身不存状态
- 判断标准:不含
android.*的代码属于 Model 层,应移出 Activity - 好处:Model 可单元测试、View 可换布局不动代码、Controller 短小易读
练习
问题 1:“第2章 Android and Model-View-Controller”覆盖哪些正式节点和项目主线?
问题 2:怎样建立本页最小可执行实验?
问题 3:为什么只在正常点击路径运行不能证明完成?
问题 4:怎样设计能推翻当前实现的反例?
问题 5:从第4版语境迁移到现代目标SDK时如何控制变量?
问题 6:本页达到独立交接标准需要什么?
名词解释
本章出现的专业名词,用大白话再讲一遍。
- MVC
Model-View-Controller,将应用拆成三层:Model 管数据与业务逻辑,View 管界面显示,Controller 协调二者。在 Android 中,Model 对应 Kotlin data class,View 对应 layout XML,Controller 对应 Activity/Fragment。详见本章"MVC 三层"一节。
- Model
MVC 中负责数据结构和业务逻辑的层。在 Android 里用 Kotlin data class 实现,不依赖任何 Android SDK API(无
View、Context等引用)。可以被单元测试直接测试——这是 MVC 分层最大的好处。详见本章"Kotlin data class:最轻量的 Model"一节。- View(MVC中的)
MVC 中负责界面显示的层。在 Android 里特指
res/layout/下的 XML 布局文件,定义控件的位置、尺寸、样式。View 层只管怎么显示,不管显示什么——数据由 Controller 填入。详见本章"MVC 三层"一节。- Controller
MVC 中连接 Model 和 View 的协调者。在 Android 里 Activity 和 Fragment 承担此角色。Controller 接收用户操作 → 调用 Model 更新数据 → 将新数据写入 View。Controller 本身不应存业务状态。详见本章"重构:拆出 QuizState Model"一节。
- data class
Kotlin 的一种特殊类,编译器自动生成
equals()、hashCode()、toString()、copy()等方法。最适合做 Model——三行代码得到一个功能完整的数据容器。详见本章"Kotlin data class"一节。
“Android与MVC设计模式”不使用未获授权的纸书正文;InformIT出版信息与授权电子版完整目录只用于确认第四版32章、269个正式目录节点和时代语境,第四版官方勘误用于识别工具链变更。下列中文解释、图示、交互、代码与练习均为独立教学重写,平台行为再以Android Developers的一手文档复核。
为什么“Android与MVC设计模式”必须回到可观察状态
“Android与MVC设计模式”的学习结果不是记住类名,而是能预测“理解 MVC 架构模式在 Android 中的映射关系,用 Kotlin data class 创建 Model,学会分离界面、数据和逻辑。”在一次输入、一次重建和一次失败中的不同状态,并指出哪条Android合同产生差异。
第四版机制逐项深读
2. Android and Model-View-Controller
在“Android与MVC设计模式”中,验证“2. Android and Model-View-Controller”只改变一个生命周期或外部条件,保存操作、原始日志、状态快照和用户可见断言。
Creating a New Class
在“Android与MVC设计模式”中,验证“Creating a New Class”只改变一个生命周期或外部条件,保存操作、原始日志、状态快照和用户可见断言。
Model-View-Controller and Android
在“Android与MVC设计模式”中,验证“Model-View-Controller and Android”只改变一个生命周期或外部条件,保存操作、原始日志、状态快照和用户可见断言。
Updating the View Layer
在“Android与MVC设计模式”中,分析“Updating the View Layer”先冻结设备API、targetSdk和输入,再沿回调与数据流寻找首个状态分叉,不能只描述最终页面。
Updating the Controller Layer
在“Android与MVC设计模式”中,验证“Updating the Controller Layer”只改变一个生命周期或外部条件,保存操作、原始日志、状态快照和用户可见断言。
Adding an Icon
在“Android与MVC设计模式”中,“Adding an Icon”的复用不能吞掉状态语义;pressed、focused、disabled与checked都要有可辨反馈。
Screen Pixel Densities
在“Android与MVC设计模式”中,验证“Screen Pixel Densities”在日夜主题、不同密度和字体缩放下比较像素边界、对比度与状态反馈。
Running on a Device
在“Android与MVC设计模式”中,“Running on a Device”若依赖第四版时期API,应分开说明原书机制与现代平台政策,并用Android官方文档核对迁移边界。
Challenge: Add a Listener to the TextView
在“Android与MVC设计模式”中,“Challenge: Add a Listener to the TextView”用于推翻“理解 MVC 架构模式在 Android 中的映射关系,用 Kotlin data class 创建 Model,学会分离界面、数据和逻辑。”的顺利路径:先写预期,再引入一个边界输入、重建或平台差异,并用状态、日志与用户结果解释首个分叉。
Challenge: Add a Previous Button
在“Android与MVC设计模式”中,“Challenge: Add a Previous Button”用于推翻“理解 MVC 架构模式在 Android 中的映射关系,用 Kotlin data class 创建 Model,学会分离界面、数据和逻辑。”的顺利路径:先写预期,再引入一个边界输入、重建或平台差异,并用状态、日志与用户结果解释首个分叉。
Challenge: From Button to ImageButton
在“Android与MVC设计模式”中,“Challenge: From Button to ImageButton”用于推翻“理解 MVC 架构模式在 Android 中的映射关系,用 Kotlin data class 创建 Model,学会分离界面、数据和逻辑。”的顺利路径:先写预期,再引入一个边界输入、重建或平台差异,并用状态、日志与用户结果解释首个分叉。
“Android与MVC设计模式”验收回顾
“Android与MVC设计模式”只有在构建指纹、用户操作、状态快照、原始日志和行为断言能够从相同基线再次得到相同断言时才通过;第四版机制与现代平台政策分别记录,不用新API名称掩盖旧行为。