[安卓] Kotlin中的架构演进:从MVC到MVVM

从Java到Kotlin,不仅仅是语法的升级,更是架构思维的进化。本文将带你全面掌握Kotlin在Android开发中三种核心架构模式:MVC、MVP、MVVM,揭示如何用Kotlin特性写出更优雅的架构代码。

引言:为什么Android开发者需要架构模式?

如果你有过Java Android开发经验,一定遇到过这些问题:Activity/Fragment代码臃肿(动辄上千行)、业务逻辑与UI逻辑混杂、测试困难、代码难以维护。架构模式正是为了解决这些问题而生的。而Kotlin,以其简洁的语法和强大的特性,让这些架构模式的实现变得更加优雅。

让我们从一个真实案例开始------用户登录模块,看看在不同架构模式下的演变:

kotlin 复制代码
// 糟糕的无架构代码(Java风格)
class BadLoginActivity : AppCompatActivity() {
    private lateinit var editUsername: EditText
    private lateinit var editPassword: EditText
    private lateinit var buttonLogin: Button
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        
        editUsername = findViewById(R.id.edit_username)
        editPassword = findViewById(R.id.edit_password)
        buttonLogin = findViewById(R.id.button_login)
        
        buttonLogin.setOnClickListener {
            // 1. UI逻辑
            val username = editUsername.text.toString()
            val password = editPassword.text.toString()
            
            if (username.isEmpty() || password.isEmpty()) {
                Toast.makeText(this, "用户名或密码不能为空", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            
            // 2. 业务逻辑
            val isValid = validateCredentials(username, password)
            if (!isValid) {
                Toast.makeText(this, "用户名或密码格式错误", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }
            
            // 3. 网络请求
            showLoading()
            Thread {
                // 模拟网络请求
                Thread.sleep(2000)
                
                runOnUiThread {
                    hideLoading()
                    if (username == "admin" && password == "123456") {
                        Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
                        startActivity(Intent(this, MainActivity::class.java))
                    } else {
                        Toast.makeText(this, "用户名或密码错误", Toast.LENGTH_SHORT).show()
                    }
                }
            }.start()
        }
    }
    
    private fun validateCredentials(username: String, password: String): Boolean {
        return username.length >= 3 && password.length >= 6
    }
    
    private fun showLoading() { /* 显示加载动画 */ }
    private fun hideLoading() { /* 隐藏加载动画 */ }
}

这样的代码有太多问题:职责不清、难以测试、回调地狱。接下来,让我们看看如何用架构模式重构它。

一、MVC模式:最基础的分层架构

1.1 MVC概念解析

MVC(Model-View-Controller) 是最经典的架构模式,将应用分为三层:

  • Model(模型):数据和业务逻辑
  • View(视图):UI展示
  • Controller(控制器):处理用户输入,协调Model和View

在Android中,通常:

  • View = XML布局 + Activity/Fragment(视图部分)
  • Controller = Activity/Fragment(控制逻辑部分)
  • Model = 数据类、仓库、业务逻辑类
kotlin 复制代码
// Kotlin MVC实现示例
// ==================== Model层 ====================
data class User(val username: String, val password: String)

class LoginModel {
    // 业务逻辑
    fun validateCredentials(username: String, password: String): Boolean {
        return username.length >= 3 && password.length >= 6
    }
    
    // 模拟网络请求
    suspend fun login(username: String, password: String): Boolean {
        delay(2000) // 模拟网络延迟
        return username == "admin" && password == "123456"
    }
}

// ==================== View层 ====================
// activity_login.xml (UI布局)

// ==================== Controller层 ====================
class LoginController(private val view: LoginView) {
    private val model = LoginModel()
    
    fun onLoginClicked(username: String, password: String) {
        // 1. 验证输入
        if (username.isEmpty() || password.isEmpty()) {
            view.showError("用户名或密码不能为空")
            return
        }
        
        if (!model.validateCredentials(username, password)) {
            view.showError("用户名或密码格式错误")
            return
        }
        
        // 2. 执行登录
        view.showLoading()
        
        // 使用协程处理异步
        view.launchOnLifecycle {
            try {
                val success = model.login(username, password)
                if (success) {
                    view.onLoginSuccess()
                } else {
                    view.showError("用户名或密码错误")
                }
            } catch (e: Exception) {
                view.showError("网络错误: ${e.message}")
            } finally {
                view.hideLoading()
            }
        }
    }
}

interface LoginView {
    fun showLoading()
    fun hideLoading()
    fun showError(message: String)
    fun onLoginSuccess()
    fun launchOnLifecycle(block: suspend () -> Unit)
}

// ==================== Activity(View+Controller混合)====================
class LoginActivity : AppCompatActivity(), LoginView {
    private lateinit var controller: LoginController
    private lateinit var binding: ActivityLoginBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        controller = LoginController(this)
        
        binding.buttonLogin.setOnClickListener {
            val username = binding.editUsername.text.toString()
            val password = binding.editPassword.text.toString()
            controller.onLoginClicked(username, password)
        }
    }
    
    override fun showLoading() {
        binding.progressBar.visibility = View.VISIBLE
        binding.buttonLogin.isEnabled = false
    }
    
    override fun hideLoading() {
        binding.progressBar.visibility = View.GONE
        binding.buttonLogin.isEnabled = true
    }
    
    override fun showError(message: String) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
    }
    
    override fun onLoginSuccess() {
        Toast.makeText(this, "登录成功", Toast.LENGTH_SHORT).show()
        startActivity(Intent(this, MainActivity::class.java))
        finish()
    }
    
    override fun launchOnLifecycle(block: suspend () -> Unit) {
        lifecycleScope.launch {
            block()
        }
    }
}

1.2 MVC的优缺点分析

优点:

  1. 结构清晰,简单易懂
  2. 分离了数据和展示
  3. 适合小型项目

缺点:

  1. Activity/Fragment既是View又是Controller,容易变得臃肿
  2. View和Model有直接或间接耦合
  3. 单元测试困难

Kotlin带来的改进:

  • 协程简化异步操作,避免回调地狱
  • 扩展函数可以抽离通用UI逻辑
  • 数据类简化Model定义

二、MVP模式:关注点分离的进化

2.1 MVP概念解析

MVP(Model-View-Presenter) 是对MVC的改进,核心变化:

  • Presenter 替代Controller,负责所有业务逻辑
  • View 只负责UI展示,变成被动接口
  • Model 保持不变

关键特点:View和Model完全解耦,通过Presenter通信。

kotlin 复制代码
// Kotlin MVP实现示例
// ==================== Contract(契约接口)====================
// 使用Kotlin的接口默认方法
interface LoginContract {
    interface View {
        fun showLoading()
        fun hideLoading()
        fun showError(message: String)
        fun onLoginSuccess()
        fun getUsername(): String
        fun getPassword(): String
        fun clearInputs()
    }
    
    interface Presenter {
        fun attachView(view: View)
        fun detachView()
        fun onLoginClicked()
        fun validateInput(): Boolean
    }
}

// ==================== Model层 ====================
data class User(val id: String, val name: String, val token: String)

interface UserRepository {
    suspend fun login(username: String, password: String): Result<User>
}

class UserRepositoryImpl : UserRepository {
    override suspend fun login(username: String, password: String): Result<User> {
        delay(2000) // 模拟网络延迟
        
        return if (username == "admin" && password == "123456") {
            Result.success(User("001", "管理员", "token_abc123"))
        } else {
            Result.failure(Exception("用户名或密码错误"))
        }
    }
}

// ==================== Presenter层 ====================
class LoginPresenter(
    private val repository: UserRepository,
    private val dispatcher: CoroutineDispatcher = Dispatchers.Main
) : LoginContract.Presenter {
    
    private var view: LoginContract.View? = null
    private var job: Job? = null
    
    override fun attachView(view: LoginContract.View) {
        this.view = view
    }
    
    override fun detachView() {
        job?.cancel()
        view = null
    }
    
    override fun onLoginClicked() {
        // 验证输入
        if (!validateInput()) {
            return
        }
        
        val view = this.view ?: return
        val username = view.getUsername()
        val password = view.getPassword()
        
        // 显示加载
        view.showLoading()
        
        // 启动协程
        job = CoroutineScope(dispatcher).launch {
            try {
                val result = withContext(Dispatchers.IO) {
                    repository.login(username, password)
                }
                
                handleLoginResult(result)
            } catch (e: Exception) {
                view?.showError("网络错误: ${e.message}")
            } finally {
                view?.hideLoading()
            }
        }
    }
    
    override fun validateInput(): Boolean {
        val view = this.view ?: return false
        
        val username = view.getUsername()
        val password = view.getPassword()
        
        return when {
            username.isEmpty() -> {
                view.showError("用户名不能为空")
                false
            }
            password.isEmpty() -> {
                view.showError("密码不能为空")
                false
            }
            username.length < 3 -> {
                view.showError("用户名至少3个字符")
                false
            }
            password.length < 6 -> {
                view.showError("密码至少6个字符")
                false
            }
            else -> true
        }
    }
    
    private fun handleLoginResult(result: Result<User>) {
        val view = this.view ?: return
        
        result.fold(
            onSuccess = { user ->
                // 登录成功
                view.onLoginSuccess()
                view.clearInputs()
                // 可以保存用户信息等操作
            },
            onFailure = { error ->
                view.showError(error.message ?: "登录失败")
            }
        )
    }
}

// ==================== View层 ====================
class LoginActivity : AppCompatActivity(), LoginContract.View {
    private lateinit var presenter: LoginPresenter
    private lateinit var binding: ActivityLoginBinding
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityLoginBinding.inflate(layoutInflater)
        setContentView(binding.root)
        
        // 初始化Presenter
        val repository = UserRepositoryImpl()
        presenter = LoginPresenter(repository)
        presenter.attachView(this)
        
        binding.buttonLogin.setOnClickListener {
            presenter.onLoginClicked()
        }
    }
    
    override fun onDestroy() {
        super.onDestroy()
        presenter.detachView()
    }
    
    // 实现View接口
    override fun showLoading() {
        binding.progressBar.visibility = View.VISIBLE
        binding.buttonLogin.isEnabled = false
        binding.buttonLogin.text = "登录中..."
    }
    
    override fun hideLoading() {
        binding.progressBar.visibility = View.GONE
        binding.buttonLogin.isEnabled = true
        binding.buttonLogin.text = "登录"
    }
    
    override fun showError(message: String) {
        Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG).show()
    }
    
    override fun onLoginSuccess() {
        startActivity(Intent(this, MainActivity::class.java))
        finish()
    }
    
    override fun getUsername(): String = binding.editUsername.text.toString()
    override fun getPassword(): String = binding.editPassword.text.toString()
    
    override fun clearInputs() {
        binding.editUsername.text.clear()
        binding.editPassword.text.clear()
    }
}

2.2 MVP的进阶优化

使用Kotlin特性优化MVP:

kotlin 复制代码
// 使用Kotlin委托简化Presenter管理
abstract class BaseMvpActivity<V : Any, P : BasePresenter<V>> : AppCompatActivity() {
    protected lateinit var presenter: P
    
    @Suppress("UNCHECKED_CAST")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        presenter = createPresenter()
        presenter.attachView(this as V)
    }
    
    override fun onDestroy() {
        super.onDestroy()
        presenter.detachView()
    }
    
    abstract fun createPresenter(): P
}

// 使用泛型和反射简化Presenter创建
abstract class BasePresenter<V : Any> {
    protected var view: V? = null
    
    fun attachView(view: V) {
        this.view = view
        onViewAttached()
    }
    
    fun detachView() {
        onViewDetached()
        this.view = null
    }
    
    protected open fun onViewAttached() {}
    protected open fun onViewDetached() {}
    
    protected fun withView(block: (V) -> Unit) {
        view?.let(block)
    }
}

// 使用扩展函数简化UI操作
fun LoginContract.View.showSuccessAndNavigate(message: String) {
    showMessage(message) // 假设有showMessage方法
    onLoginSuccess()
}

// 使用密封类处理结果
sealed class LoginResult {
    data class Success(val user: User) : LoginResult()
    data class Error(val message: String) : LoginResult()
    object Loading : LoginResult()
    object InvalidInput : LoginResult()
}

// 响应式Presenter
class ReactiveLoginPresenter(
    private val repository: UserRepository
) : LoginContract.Presenter {
    
    private val _state = MutableStateFlow<LoginResult>(LoginResult.InvalidInput)
    val state: StateFlow<LoginResult> = _state.asStateFlow()
    
    override fun onLoginClicked() {
        // 通过状态流通知View更新
    }
}

2.3 MVP的优缺点分析

优点:

  1. View和Model完全解耦
  2. Presenter可测试(纯Java/Kotlin对象)
  3. 职责清晰,View只处理UI

缺点:

  1. 需要大量接口,代码量增加
  2. Presenter可能变得臃肿
  3. 生命周期管理复杂

Kotlin改进:

  • 接口默认方法减少模板代码
  • 协程简化异步和生命周期管理
  • 扩展函数抽离通用逻辑
  • 委托属性管理Presenter

三、MVVM模式:数据驱动的现代架构

3.1 MVVM概念解析

MVVM(Model-View-ViewModel) 是目前Android官方推荐的架构:

  • ViewModel:管理UI相关数据, survives配置变化
  • View:观察ViewModel的数据变化,自动更新
  • Model:数据源和业务逻辑

核心:数据绑定(Data Binding)或视图绑定(View Binding)+ LiveData/StateFlow。

kotlin 复制代码
// Kotlin MVVM实现示例(使用Android Architecture Components)
// ==================== Model层 ====================
data class User(
    val id: String,
    val name: String,
    val email: String,
    val token: String
)

sealed class ApiResult<out T> {
    data class Success<T>(val data: T) : ApiResult<T>()
    data class Error(val message: String) : ApiResult<Nothing>()
    object Loading : ApiResult<Nothing>()
}

interface AuthRepository {
    suspend fun login(username: String, password: String): ApiResult<User>
    suspend fun register(username: String, password: String, email: String): ApiResult<User>
    fun logout()
}

class AuthRepositoryImpl @Inject constructor(
    private val apiService: AuthApiService,
    private val prefs: SharedPreferences
) : AuthRepository {
    
    override suspend fun login(username: String, password: String): ApiResult<User> {
        return try {
            // 实际网络请求
            val response = apiService.login(LoginRequest(username, password))
            if (response.isSuccessful) {
                val user = response.body()?.toUser() ?: throw Exception("数据解析错误")
                
                // 保存token
                prefs.edit().putString("auth_token", user.token).apply()
                
                ApiResult.Success(user)
            } else {
                ApiResult.Error("登录失败: ${response.message()}")
            }
        } catch (e: Exception) {
            ApiResult.Error("网络错误: ${e.message}")
        }
    }
    
    // 其他方法...
}

// ==================== ViewModel层 ====================
@HiltViewModel
class LoginViewModel @Inject constructor(
    private val repository: AuthRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    // UI状态(使用密封类)
    private val _uiState = MutableStateFlow<LoginUiState>(LoginUiState.Idle)
    val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
    
    // 表单数据
    val username = savedStateHandle.getStateFlow("username", "")
    val password = savedStateHandle.getStateFlow("password", "")
    
    // 表单验证状态
    val isFormValid = combine(username, password) { user, pass ->
        user.isNotBlank() && pass.isNotBlank() && user.length >= 3 && pass.length >= 6
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = false
    )
    
    // 用户操作
    fun onUsernameChanged(newValue: String) {
        savedStateHandle["username"] = newValue
    }
    
    fun onPasswordChanged(newValue: String) {
        savedStateHandle["password"] = newValue
    }
    
    fun onLoginClick() {
        viewModelScope.launch {
            _uiState.value = LoginUiState.Loading
            
            val result = repository.login(username.value, password.value)
            
            _uiState.value = when (result) {
                is ApiResult.Success -> LoginUiState.Success(result.data)
                is ApiResult.Error -> LoginUiState.Error(result.message)
                else -> LoginUiState.Idle
            }
        }
    }
    
    fun resetState() {
        _uiState.value = LoginUiState.Idle
    }
}

// UI状态密封类
sealed class LoginUiState {
    object Idle : LoginUiState()
    object Loading : LoginUiState()
    data class Success(val user: User) : LoginUiState()
    data class Error(val message: String) : LoginUiState()
}

// ==================== View层 ====================
@AndroidEntryPoint
class LoginFragment : Fragment() {
    private var _binding: FragmentLoginBinding? = null
    private val binding get() = _binding!!
    
    private val viewModel: LoginViewModel by viewModels()
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        _binding = FragmentLoginBinding.inflate(inflater, container, false)
        return binding.root
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        setupViews()
        observeViewModel()
    }
    
    private fun setupViews() {
        // 使用数据绑定(也可用视图绑定)
        binding.apply {
            // 双向数据绑定
            editUsername.doAfterTextChanged { text ->
                viewModel.onUsernameChanged(text.toString())
            }
            
            editPassword.doAfterTextChanged { text ->
                viewModel.onPasswordChanged(text.toString())
            }
            
            buttonLogin.setOnClickListener {
                viewModel.onLoginClick()
            }
            
            // 根据表单状态更新按钮
            viewLifecycleOwner.lifecycleScope.launch {
                viewModel.isFormValid.collect { isValid ->
                    buttonLogin.isEnabled = isValid
                    buttonLogin.alpha = if (isValid) 1f else 0.5f
                }
            }
        }
    }
    
    private fun observeViewModel() {
        viewLifecycleOwner.lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect { state ->
                    when (state) {
                        is LoginUiState.Idle -> handleIdleState()
                        is LoginUiState.Loading -> handleLoadingState()
                        is LoginUiState.Success -> handleSuccessState(state.user)
                        is LoginUiState.Error -> handleErrorState(state.message)
                    }
                }
            }
        }
    }
    
    private fun handleIdleState() {
        binding.progressBar.hide()
        binding.buttonLogin.isEnabled = true
    }
    
    private fun handleLoadingState() {
        binding.progressBar.show()
        binding.buttonLogin.isEnabled = false
    }
    
    private fun handleSuccessState(user: User) {
        binding.progressBar.hide()
        showSuccessMessage("欢迎回来,${user.name}!")
        
        // 导航到主界面
        findNavController().navigate(
            LoginFragmentDirections.actionLoginFragmentToHomeFragment(user)
        )
    }
    
    private fun handleErrorState(message: String) {
        binding.progressBar.hide()
        binding.buttonLogin.isEnabled = true
        showErrorMessage(message)
    }
    
    private fun showSuccessMessage(message: String) {
        Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG)
            .setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.success))
            .show()
    }
    
    private fun showErrorMessage(message: String) {
        Snackbar.make(binding.root, message, Snackbar.LENGTH_LONG)
            .setBackgroundTint(ContextCompat.getColor(requireContext(), R.color.error))
            .setAction("重试") {
                viewModel.onLoginClick()
            }
            .show()
    }
    
    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

3.2 MVVM的进阶:使用Jetpack Compose

kotlin 复制代码
// Jetpack Compose + MVVM + Kotlin的现代组合
@Composable
fun LoginScreen(
    viewModel: LoginViewModel = hiltViewModel(),
    onLoginSuccess: (User) -> Unit
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    val isFormValid by viewModel.isFormValid.collectAsStateWithLifecycle()
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        // Logo
        Icon(
            painter = painterResource(R.drawable.ic_logo),
            contentDescription = "Logo",
            modifier = Modifier.size(100.dp),
            tint = MaterialTheme.colorScheme.primary
        )
        
        Spacer(modifier = Modifier.height(32.dp))
        
        // 用户名输入
        OutlinedTextField(
            value = viewModel.username.value,
            onValueChange = viewModel::onUsernameChanged,
            label = { Text("用户名") },
            modifier = Modifier.fillMaxWidth(),
            isError = viewModel.username.value.isNotEmpty() && viewModel.username.value.length < 3
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // 密码输入
        OutlinedTextField(
            value = viewModel.password.value,
            onValueChange = viewModel::onPasswordChanged,
            label = { Text("密码") },
            modifier = Modifier.fillMaxWidth(),
            visualTransformation = PasswordVisualTransformation(),
            isError = viewModel.password.value.isNotEmpty() && viewModel.password.value.length < 6
        )
        
        Spacer(modifier = Modifier.height(32.dp))
        
        // 登录按钮
        Button(
            onClick = viewModel::onLoginClick,
            modifier = Modifier.fillMaxWidth(),
            enabled = isFormValid && uiState !is LoginUiState.Loading
        ) {
            if (uiState is LoginUiState.Loading) {
                CircularProgressIndicator(
                    modifier = Modifier.size(20.dp),
                    color = MaterialTheme.colorScheme.onPrimary
                )
                Spacer(modifier = Modifier.width(8.dp))
                Text("登录中...")
            } else {
                Text("登录")
            }
        }
        
        // 处理状态
        when (val state = uiState) {
            is LoginUiState.Error -> {
                Spacer(modifier = Modifier.height(16.dp))
                Text(
                    text = state.message,
                    color = MaterialTheme.colorScheme.error,
                    style = MaterialTheme.typography.bodySmall
                )
            }
            is LoginUiState.Success -> {
                LaunchedEffect(state) {
                    onLoginSuccess(state.user)
                }
            }
            else -> {}
        }
    }
}

3.3 MVVM的优缺点分析

优点:

  1. 数据驱动UI,自动更新
  2. ViewModel survives配置变化
  3. 配合Data Binding/Compose减少模板代码
  4. 官方支持,生态完善

缺点:

  1. 学习曲线较陡
  2. Data Binding调试困难
  3. 过度使用可能造成内存泄漏

Kotlin改进:

  • 协程Flow替代LiveData(更强大)
  • 密封类优雅处理状态
  • 扩展函数简化数据绑定
  • 委托属性管理ViewModel

四、架构模式对比与选型指南

4.1 三种架构模式对比

特性 MVC MVP MVVM
核心思想 分层 关注点分离 数据驱动
View职责 展示+部分控制 仅展示 观察数据变化
测试难度 困难 中等 容易
代码量 中等
学习成本 中等
适合场景 小型项目 中型项目 中大型项目
Kotlin适配 一般 良好 优秀

4.2 实际项目选型建议

kotlin 复制代码
// 项目架构决策器
object ArchitectureDecider {
    
    fun decideArchitecture(project: ProjectInfo): Architecture {
        return when {
            // 小型工具类应用
            project.teamSize <= 2 && project.complexity == Complexity.SIMPLE 
                -> Architecture.MVC
            
            // 中型商业应用
            project.teamSize in 3..5 && project.hasUnitTest 
                -> Architecture.MVP
            
            // 大型复杂应用
            project.teamSize > 5 && project.longTermMaintenance 
                -> Architecture.MVVM
            
            // 使用Jetpack Compose
            project.usesCompose && project.minSdk >= 21 
                -> Architecture.MVVM_WITH_COMPOSE
            
            // 混合架构(过渡期)
            else -> Architecture.HYBRID
        }
    }
}

data class ProjectInfo(
    val teamSize: Int,
    val complexity: Complexity,
    val hasUnitTest: Boolean,
    val longTermMaintenance: Boolean,
    val usesCompose: Boolean = false,
    val minSdk: Int = 21
)

enum class Complexity { SIMPLE, MEDIUM, COMPLEX }
enum class Architecture { MVC, MVP, MVVM, MVVM_WITH_COMPOSE, HYBRID }

4.3 混合架构实践

在实际项目中,常常需要混合使用多种架构:

kotlin 复制代码
// 混合架构示例:MVVM + Clean Architecture
@HiltViewModel
class UserProfileViewModel @Inject constructor(
    private val getUserUseCase: GetUserUseCase,
    private val updateUserUseCase: UpdateUserUseCase,
    savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    // UI状态(MVVM)
    private val _uiState = MutableStateFlow<UserProfileUiState>(UserProfileUiState.Loading)
    val uiState: StateFlow<UserProfileUiState> = _uiState.asStateFlow()
    
    // 从Clean Architecture的UseCase获取数据
    fun loadUserProfile(userId: String) {
        viewModelScope.launch {
            _uiState.value = UserProfileUiState.Loading
            
            val result = getUserUseCase.invoke(userId)
            _uiState.value = when (result) {
                is Result.Success -> UserProfileUiState.Success(result.data)
                is Result.Error -> UserProfileUiState.Error(result.message)
            }
        }
    }
    
    // 使用MVP模式的契约接口进行模块间通信
    fun updateProfile(data: ProfileData) {
        viewModelScope.launch {
            val result = updateUserUseCase.invoke(data)
            if (result is Result.Success) {
                // 通知其他模块(类似MVP的接口回调)
                eventChannel.send(ProfileUpdateEvent.Success)
            }
        }
    }
    
    private val eventChannel = Channel<ProfileUpdateEvent>()
    val events = eventChannel.receiveAsFlow()
}

// Clean Architecture的UseCase(领域层)
class GetUserUseCase @Inject constructor(
    private val userRepository: UserRepository
) {
    suspend operator fun invoke(userId: String): Result<User> {
        return userRepository.getUserById(userId)
    }
}

// 事件密封类(类似MVP的契约接口)
sealed class ProfileUpdateEvent {
    object Success : ProfileUpdateEvent()
    data class Error(val message: String) : ProfileUpdateEvent()
}

五、Kotlin在架构中的最佳实践

5.1 使用Kotlin特性优化架构代码

kotlin 复制代码
// 1. 使用密封类处理状态(替代枚举+接口)
sealed class Resource<out T> {
    data class Success<T>(val data: T) : Resource<T>()
    data class Error(val exception: Throwable) : Resource<Nothing>()
    object Loading : Resource<Nothing>()
    
    // 扩展函数处理不同状态
    fun onSuccess(block: (T) -> Unit): Resource<T> = this.also {
        if (this is Success) block(data)
    }
    
    fun onError(block: (Throwable) -> Unit): Resource<T> = this.also {
        if (this is Error) block(exception)
    }
}

// 2. 使用扩展函数简化架构组件
fun <T> Flow<Resource<T>>.bindToView(
    lifecycleOwner: LifecycleOwner,
    onSuccess: (T) -> Unit,
    onError: (Throwable) -> Unit,
    onLoading: () -> Unit = {}
) {
    lifecycleOwner.lifecycleScope.launch {
        lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
            this@bindToView.collect { resource ->
                when (resource) {
                    is Resource.Loading -> onLoading()
                    is Resource.Success -> onSuccess(resource.data)
                    is Resource.Error -> onError(resource.exception)
                }
            }
        }
    }
}

// 3. 使用委托属性管理依赖
interface Injectable {
    val injector: Injector get() = InjectorProvider.injector
}

class MainViewModel : ViewModel(), Injectable {
    // 延迟注入依赖
    private val repository: UserRepository by inject { UserRepositoryImpl() }
    
    // 或者使用Koin/Koin/Hilt
    private val useCase: GetUserUseCase by inject()
}

// 4. 使用内联函数减少模板代码
inline fun <T> ViewModel.launchWithState(
    stateFlow: MutableStateFlow<Resource<T>>,
    crossinline block: suspend () -> T
) {
    viewModelScope.launch {
        stateFlow.value = Resource.Loading
        try {
            val result = block()
            stateFlow.value = Resource.Success(result)
        } catch (e: Exception) {
            stateFlow.value = Resource.Error(e)
        }
    }
}

5.2 架构组件与Kotlin协程的完美结合

kotlin 复制代码
// 使用协程Flow构建响应式架构
class ProductListViewModel @Inject constructor(
    private val getProductsUseCase: GetProductsUseCase,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {
    
    // 搜索词(状态保存)
    private val searchQuery = savedStateHandle.getStateFlow("searchQuery", "")
    
    // 排序方式
    private val sortOrder = MutableStateFlow(SortOrder.DEFAULT)
    
    // 产品列表(自动响应搜索词和排序变化)
    val products: StateFlow<Resource<List<Product>>> = combine(
        searchQuery.debounce(300), // 防抖
        sortOrder
    ) { query, order ->
        Pair(query, order)
    }.flatMapLatest { (query, order) ->
        getProductsUseCase(query, order)
            .map { Resource.Success(it) }
            .catch { emit(Resource.Error(it)) }
            .onStart { emit(Resource.Loading) }
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = Resource.Loading
    )
    
    fun onSearchQueryChanged(query: String) {
        savedStateHandle["searchQuery"] = query
    }
    
    fun onSortOrderChanged(order: SortOrder) {
        sortOrder.value = order
    }
}

六、常见面试题

基础概念题

  1. MVC、MVP和MVVM三种架构模式的主要区别是什么?各有什么优缺点?

    • 答案要点:MVC中Controller处理业务逻辑,View和Model可能耦合;MVP中Presenter处理所有逻辑,View被动;MVVM中ViewModel管理数据,View观察数据变化。MVC简单但易臃肿,MVP解耦但接口多,MVVM数据驱动但学习成本高。
  2. 在MVVM架构中,LiveData和StateFlow有什么区别?为什么推荐使用StateFlow?

    • 答案要点:LiveData是生命周期感知的,但功能有限;StateFlow基于协程,支持复杂的变换操作、背压处理、线程切换。StateFlow更强大、更符合Kotlin协程生态、支持测试更方便。
  3. 如何防止在MVVM架构中出现内存泄漏?ViewModel的scope是如何管理的?

    • 答案要点:1) 使用ViewLifecycleOwner而非Activity;2) 在onDestroyView中清除绑定;3) 使用repeatOnLifecycle收集Flow;4) ViewModel由ViewModelStore管理,在Activity重建时保持,在Finish时销毁。

实践应用题

  1. 将一个使用回调的MVC网络请求模块,改造成使用协程的MVVM架构,请写出关键代码。

    • 答案要点

      kotlin 复制代码
      // 改造前(MVC+回调)
      class UserManager {
          fun fetchUser(callback: (User) -> Unit) { ... }
      }
      
      // 改造后(MVVM+协程)
      class UserRepository {
          suspend fun fetchUser(): User = withContext(Dispatchers.IO) { ... }
      }
      
      class UserViewModel : ViewModel() {
          private val _user = MutableStateFlow<Resource<User>>(Resource.Loading)
          val user: StateFlow<Resource<User>> = _user.asStateFlow()
          
          fun loadUser() {
              viewModelScope.launch {
                  _user.value = Resource.Loading
                  try {
                      val user = repository.fetchUser()
                      _user.value = Resource.Success(user)
                  } catch (e: Exception) {
                      _user.value = Resource.Error(e)
                  }
              }
          }
      }
  2. 在MVP架构中,如何处理Presenter的生命周期?如何避免内存泄漏?

    • 答案要点:1) 在onCreate中创建Presenter,onDestroy中解绑;2) 使用弱引用持有View;3) 使用BasePresenter管理生命周期;4) 在View销毁时取消所有异步任务。示例:Presenter中定义attach/detach方法,Activity在对应生命周期调用。
  3. 如何设计一个支持离线功能的MVVM架构?请描述数据流向。

    • 答案要点:采用Repository模式,数据流向:View → ViewModel → Repository → (优先缓存 → 网络)。关键点:1) 单一数据源;2) 缓存策略;3) 网络状态监测;4) 数据同步机制。使用Room缓存,Flow实现数据更新通知。

深入原理题

  1. Data Binding和View Binding有什么区别?在MVVM中如何选择?

    • 答案要点:DataBinding支持双向绑定和表达式,但编译速度慢、调试困难;ViewBinding只生成视图引用,简单高效。选择:简单场景用ViewBinding,复杂数据绑定用DataBinding,或两者混合使用(主要用ViewBinding,局部用DataBinding)。
  2. 在大型项目中,如何组织MVVM的模块结构?如何避免ViewModel臃肿?

    • 答案要点:按功能模块划分:feature包(View+ViewModel)、data包(Repository+Model)、domain包(UseCase)。避免ViewModel臃肿:1) 使用UseCase封装业务逻辑;2) 使用State管理UI状态;3) 使用Event处理一次性事件;4) 提取基类ViewModel。
  3. 如何测试MVVM架构中的ViewModel?如何模拟依赖?

    • 答案要点:使用JUnit + MockK + Turbine。步骤:1) 使用依赖注入(Hilt/Koin);2) 测试中提供模拟依赖;3) 测试StateFlow的状态变化;4) 测试协程执行逻辑。示例:使用TestCoroutineDispatcher,验证状态流转。

总结

从MVC到MVVM,不仅是架构模式的演进,更是开发思维从"界面驱动"到"数据驱动"的转变。Kotlin作为现代编程语言,其协程、扩展函数、密封类、Flow等特性,与MVVM架构完美契合,让Android开发变得更加高效、优雅。

对于从Java转向Kotlin的开发者,建议的学习路径是:

  1. 先理解MVC:掌握基础分层思想
  2. 实践MVP:体会接口契约和测试优势
  3. 深入MVVM:掌握数据驱动和响应式编程
  4. 融合最佳实践:结合Clean Architecture、UseCase等

在实际项目中,没有"最好"的架构,只有"最适合"的架构。小型工具类应用可能MVC就够了,中型商业应用适合MVP,大型复杂应用则推荐MVVM。重要的是保持架构一致性和可维护性。

随着Jetpack Compose的成熟,"Compose + MVVM"正在成为新的标准范式。掌握Kotlin和现代Android架构,就能在移动开发的道路上走得更远。


相关推荐
凌云拓界6 小时前
前端开发的“平衡木”:在取舍之间找到最优解
前端·性能优化·架构·前端框架·代码规范·设计规范
冬奇Lab6 小时前
应用异常退出实战分析:一次"幽灵杀手"引发的车载系统故障排查
android·性能优化·debug
nbsaas-boot7 小时前
多租户低代码 SaaS 平台架构白皮书
低代码·架构
葡萄城技术团队7 小时前
从 Shortcut 的爆火,看 AI 时代电子表格的技术底座与架构演进
人工智能·架构
两万五千个小时8 小时前
构建mini Claude Code:12 - 从「文件冲突」到「分身协作」:Worktree 如何让多 Agent 安全并行
人工智能·python·架构
一拳不是超人9 小时前
从“必选项”到“性能包袱”:为什么现代框架开始“抛弃”虚拟 DOM?
前端·javascript·架构
云器科技9 小时前
从“数据中台“到“数智基建“:一树药业的湖仓架构升级实践
大数据·架构·湖仓平台
爱学习的大牛12310 小时前
GPU架构学习
学习·架构·gpu
生成论实验室10 小时前
即事经智能:一种基于生成易算的通用智能新范式(书)
人工智能·神经网络·算法·架构·信息与通信
Ehtan_Zheng10 小时前
如何简化状态和实体映射Kotlin接口,委托和协变泛型
android