[安卓] 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架构,就能在移动开发的道路上走得更远。


相关推荐
JMchen1232 小时前
AR Core与CameraX的融合:测量应用从原理到实现
android·经验分享·程序人生·ar·移动开发·android studio·camerax
June bug2 小时前
【领域知识】一个休闲游戏产品(安卓和iOS)从0到1
android·ios
zgyhc20502 小时前
【Android Audio】Android Audio有线设备插拔监听机制
android
ZHANG13HAO2 小时前
android13 系统强制wifi连接到内网,假装具有互联网能力
android
七夜zippoe2 小时前
分布式配置中心终极对决 Spring Cloud Config与Apollo架构深度解析
分布式·架构·springcloud·apollo·配置中心
有诺千金2 小时前
SpringBoot3的前后端分离架构中使用SpringSecurity的思路
spring boot·架构
2501_915106322 小时前
iOS 如何绕过 ATS 发送请求,iOS调试
android·ios·小程序·https·uni-app·iphone·webview
切糕师学AI2 小时前
ARM 架构中的 CurrentEL
arm开发·架构
vx-bot5556662 小时前
企业微信接口在AI智能体与知识库集成中的架构实践
人工智能·架构·企业微信