# MVVM 四层架构设计文档

> 本文档说明新大洲 Android 项目采用的 MVVM 四层架构设计

## 目录

- [一、架构概述](#一架构概述)
- [二、四层架构详解](#二四层架构详解)
- [三、目录结构规范](#三目录结构规范)
- [四、数据流向](#四数据流向)
- [五、代码示例](#五代码示例)
- [六、依赖关系](#六依赖关系)
- [七、最佳实践](#七最佳实践)

---

## 一、架构概述

### 1.1 架构说明

本项目采用 **MVVM 四层架构**，基于 Clean Architecture 和 Android 架构组件，将代码分为以下四层：

```
┌─────────────────────────────────────────┐
│         UI/Presentation 层               │
│      (Fragment + ViewModel)              │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│            Domain 层                     │
│         (UseCase + Model)                │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│          Repository 层                   │
│      (数据仓库，统一数据源)                │
└──────────────┬──────────────────────────┘
               │
               ▼
┌─────────────────────────────────────────┐
│            Data 层                       │
│    (Remote + Local DataSource)          │
└─────────────────────────────────────────┘
```

### 1.2 架构优势

1. **职责分离**：每一层都有明确的职责，互不干扰
2. **易于测试**：各层独立，便于单元测试
3. **可维护性**：代码结构清晰，易于维护和扩展
4. **可复用性**：Domain 层不依赖 Android 框架，可在其他平台复用
5. **数据统一**：Repository 层统一管理数据源，处理缓存策略

### 1.3 与传统 MVVM 的区别

**传统三层 MVVM：**
```
View → ViewModel → Repository → API
```

**四层 MVVM（Clean Architecture）：**
```
View → ViewModel → UseCase → Repository → DataSource
```

**主要改进：**
- 新增 **Domain 层**：封装业务逻辑，与 UI 和 Data 解耦
- 新增 **DataSource 层**：将数据源（Remote/Local）与 Repository 分离
- 更好的可测试性和可维护性

---

## 二、四层架构详解

### 2.1 UI/Presentation 层

**职责：**
- 展示 UI，处理用户交互
- 观察 ViewModel 的状态变化，更新 UI
- 不包含业务逻辑

**包含内容：**
- Fragment/Activity
- ViewModel
- UI State（Sealed Class）

**依赖关系：**
- 只依赖 Domain 层（通过 UseCase）
- 不直接依赖 Data 层

**示例结构：**
```
ui/
├── login/
│   └── LoginFragment.kt
├── viewmodel/
│   ├── LoginViewModel.kt
│   └── LoginViewModelFactory.kt
└── state/
    └── LoginState.kt
```

### 2.2 Domain 层

**职责：**
- 封装业务逻辑
- 定义业务实体模型
- 定义 UseCase（用例）

**包含内容：**
- UseCase（用例）
- Domain Model（业务模型）
- Repository Interface（可选）

**依赖关系：**
- 不依赖任何 Android 框架
- 只依赖 Repository Interface（接口，不依赖实现）

**示例结构：**
```
domain/
├── model/
│   └── User.kt
└── usecase/
    ├── LoginUseCase.kt
    └── RegisterUseCase.kt
```

### 2.3 Repository 层

**职责：**
- 统一数据源管理
- 协调 Remote 和 Local 数据源
- 处理数据缓存策略
- 数据转换（Data Model → Domain Model）

**包含内容：**
- Repository 实现类
- 数据转换逻辑

**依赖关系：**
- 依赖 Domain 层（UseCase）
- 依赖 Data 层（RemoteDataSource + LocalDataSource）

**示例结构：**
```
data/
└── repository/
    └── AuthRepository.kt
```

### 2.4 Data 层

**职责：**
- 提供数据源接口和实现
- 处理网络请求（Remote）
- 处理本地存储（Local）
- 定义数据模型（Data Model）

**包含内容：**
- RemoteDataSource（远程数据源）
- LocalDataSource（本地数据源）
- Data Model（数据模型）
- API 接口

**依赖关系：**
- 只依赖 Android 框架和第三方库
- 不依赖其他业务层

**示例结构：**
```
data/
├── remote/
│   └── AuthRemoteDataSource.kt
├── local/
│   ├── AuthLocalDataSource.kt
│   └── TokenManager.kt
├── model/
│   ├── LoginRequest.kt
│   └── LoginResponse.kt
└── api/
    └── AuthApi.kt
```

---

## 三、目录结构规范

### 3.1 模块目录结构

每个功能模块应遵循以下目录结构：

```
module_name/
├── ui/                          # UI 层
│   ├── feature_name/
│   │   └── FeatureFragment.kt
│   ├── viewmodel/
│   │   ├── FeatureViewModel.kt
│   │   └── FeatureViewModelFactory.kt
│   └── state/
│       └── FeatureState.kt
│
├── domain/                      # Domain 层
│   ├── model/
│   │   └── DomainModel.kt
│   └── usecase/
│       └── FeatureUseCase.kt
│
├── data/                        # Data 层
│   ├── repository/
│   │   └── FeatureRepository.kt
│   ├── remote/
│   │   └── FeatureRemoteDataSource.kt
│   ├── local/
│   │   └── FeatureLocalDataSource.kt
│   ├── model/
│   │   ├── FeatureRequest.kt
│   │   └── FeatureResponse.kt
│   └── api/
│       └── FeatureApi.kt
│
├── di/                          # 依赖注入（可选）
│   └── FeatureModule.kt
│
└── constant/                    # 常量（可选）
    └── FeatureConstants.kt
```

### 3.2 实际示例（用户模块）

```
user/
├── ui/
│   ├── login/
│   │   └── LoginFragment.kt
│   ├── register/
│   │   └── RegisterFragment.kt
│   ├── viewmodel/
│   │   ├── LoginViewModel.kt
│   │   ├── LoginViewModelFactory.kt
│   │   ├── RegisterViewModel.kt
│   │   └── RegisterViewModelFactory.kt
│   └── state/
│       ├── LoginState.kt
│       └── RegisterState.kt
│
├── domain/
│   ├── model/
│   │   └── User.kt
│   └── usecase/
│       ├── LoginUseCase.kt
│       └── RegisterUseCase.kt
│
├── data/
│   ├── repository/
│   │   └── AuthRepository.kt
│   ├── remote/
│   │   └── AuthRemoteDataSource.kt
│   ├── local/
│   │   ├── AuthLocalDataSource.kt
│   │   └── TokenManager.kt
│   ├── model/
│   │   ├── LoginRequest.kt
│   │   ├── LoginResponse.kt
│   │   └── RegisterRequest.kt
│   └── api/
│       └── AuthApi.kt
│
└── constant/
    ├── ApiConstants.kt
    └── ValidationConstants.kt
```

---

## 四、数据流向

### 4.1 请求数据流向

```
用户操作
  ↓
Fragment (UI)
  ↓ 调用
ViewModel.login()
  ↓ 调用
UseCase.invoke()
  ↓ 调用
Repository.login()
  ↓ 调用
RemoteDataSource.login() 或 LocalDataSource.getData()
  ↓
API/数据库
  ↓ 返回数据
Repository (转换 Data Model → Domain Model)
  ↓ 返回
UseCase (业务逻辑处理)
  ↓ 返回
ViewModel (转换为 UI State)
  ↓ 更新 StateFlow
Fragment (观察 StateFlow，更新 UI)
```

### 4.2 具体示例（登录流程）

```
1. 用户在 LoginFragment 点击登录按钮
   ↓
2. LoginFragment 调用 viewModel.login(mobile, password)
   ↓
3. LoginViewModel 调用 loginUseCase(mobile, password)
   ↓
4. LoginUseCase 验证输入，调用 authRepository.login()
   ↓
5. AuthRepository 调用 remoteDataSource.login()
   ↓
6. AuthRemoteDataSource 调用 authApi.login() (Retrofit)
   ↓
7. 网络请求返回 LoginResponse (Data Model)
   ↓
8. AuthRemoteDataSource 返回 Result<LoginResponse>
   ↓
9. AuthRepository 保存 Token 到 LocalDataSource
   ↓
10. AuthRepository 返回 Result<LoginResponse>
    ↓
11. LoginUseCase 返回 Result<Unit>
    ↓
12. LoginViewModel 更新 _loginState (Success/Error)
    ↓
13. LoginFragment 观察 loginState，更新 UI
```

---

## 五、代码示例

### 5.1 UI 层示例

#### LoginFragment.kt

```kotlin
package com.narutohuo.xindazhou.user.ui.login

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import com.narutohuo.xindazhou.R
import com.narutohuo.xindazhou.user.ui.viewmodel.LoginViewModel
import com.narutohuo.xindazhou.user.ui.viewmodel.LoginViewModelFactory
import kotlinx.coroutines.launch

/**
 * 登录Fragment
 */
class LoginFragment : Fragment() {
    
    private val viewModel: LoginViewModel by viewModels { 
        LoginViewModelFactory(requireContext().applicationContext as android.app.Application)
    }
    
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        return inflater.inflate(R.layout.fragment_login, container, false)
    }
    
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        // 初始化视图
        initViews(view)
        
        // 观察状态
        observeState()
    }
    
    private fun initViews(view: View) {
        // 绑定视图和设置点击事件
        view.findViewById<View>(R.id.btnLogin).setOnClickListener {
            val mobile = view.findViewById<EditText>(R.id.etMobile).text.toString()
            val password = view.findViewById<EditText>(R.id.etPassword).text.toString()
            viewModel.login(mobile, password)
        }
    }
    
    private fun observeState() {
        lifecycleScope.launch {
            viewModel.loginState.collect { state ->
                when (state) {
                    is LoginState.Idle -> { /* 初始状态 */ }
                    is LoginState.Loading -> { /* 显示加载 */ }
                    is LoginState.Success -> { /* 登录成功 */ }
                    is LoginState.Error -> { /* 显示错误 */ }
                }
            }
        }
    }
}
```

#### LoginViewModel.kt

```kotlin
package com.narutohuo.xindazhou.user.ui.viewmodel

import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.viewModelScope
import com.narutohuo.xindazhou.user.domain.usecase.LoginUseCase
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

/**
 * 登录状态
 */
sealed class LoginState {
    object Idle : LoginState()
    object Loading : LoginState()
    data class Success(val message: String) : LoginState()
    data class Error(val message: String) : LoginState()
}

/**
 * 登录ViewModel
 */
class LoginViewModel(
    application: Application,
    private val loginUseCase: LoginUseCase
) : AndroidViewModel(application) {
    
    private val _loginState = MutableStateFlow<LoginState>(LoginState.Idle)
    val loginState: StateFlow<LoginState> = _loginState
    
    fun login(mobile: String, password: String) {
        viewModelScope.launch {
            _loginState.value = LoginState.Loading
            loginUseCase(mobile, password)
                .onSuccess {
                    _loginState.value = LoginState.Success("登录成功")
                }
                .onFailure { e ->
                    _loginState.value = LoginState.Error(e.message ?: "登录失败")
                }
        }
    }
}
```

### 5.2 Domain 层示例

#### LoginUseCase.kt

```kotlin
package com.narutohuo.xindazhou.user.domain.usecase

import com.narutohuo.xindazhou.user.data.repository.AuthRepository
import com.narutohuo.xindazhou.user.domain.constant.ValidationConstants
import timber.log.Timber

/**
 * 登录用例
 * 
 * 封装登录业务逻辑
 */
class LoginUseCase(
    private val authRepository: AuthRepository
) {
    suspend operator fun invoke(mobile: String, password: String): Result<Unit> {
        // 业务逻辑验证
        if (mobile.isBlank()) {
            return Result.failure(IllegalArgumentException("手机号不能为空"))
        }
        
        if (password.length < ValidationConstants.MIN_PASSWORD_LENGTH 
            || password.length > ValidationConstants.MAX_PASSWORD_LENGTH) {
            return Result.failure(
                IllegalArgumentException("密码长度应为${ValidationConstants.MIN_PASSWORD_LENGTH}-${ValidationConstants.MAX_PASSWORD_LENGTH}位")
            )
        }
        
        // 调用 Repository
        return authRepository.login(mobile, password).map { }
    }
}
```

### 5.3 Repository 层示例

#### AuthRepository.kt

```kotlin
package com.narutohuo.xindazhou.user.data.repository

import com.narutohuo.xindazhou.user.data.local.AuthLocalDataSource
import com.narutohuo.xindazhou.user.data.model.LoginRequest
import com.narutohuo.xindazhou.user.data.model.LoginResponse
import com.narutohuo.xindazhou.user.data.remote.AuthRemoteDataSource
import timber.log.Timber

/**
 * 认证数据仓库
 * 
 * 统一管理认证相关的数据获取和缓存
 */
class AuthRepository(
    private val remoteDataSource: AuthRemoteDataSource,
    private val localDataSource: AuthLocalDataSource
) {
    
    companion object {
        private const val TAG = "AuthRepository"
    }
    
    /**
     * 用户登录
     */
    suspend fun login(mobile: String, password: String): Result<LoginResponse> {
        return try {
            val request = LoginRequest(mobile, password)
            
            // 先尝试从远程获取
            val result = remoteDataSource.login(request)
            
            // 如果成功，保存到本地
            result.onSuccess { response ->
                localDataSource.saveToken(response)
                Timber.d("$TAG - login: Token已保存")
            }
            
            result
        } catch (e: Exception) {
            Timber.e(e, "$TAG - login: 登录异常")
            Result.failure(e)
        }
    }
}
```

### 5.4 Data 层示例

#### AuthRemoteDataSource.kt

```kotlin
package com.narutohuo.xindazhou.user.data.remote

import com.narutohuo.xindazhou.user.api.AuthApi
import com.narutohuo.xindazhou.user.data.constant.ApiConstants
import com.narutohuo.xindazhou.user.data.model.LoginRequest
import com.narutohuo.xindazhou.user.data.model.LoginResponse
import timber.log.Timber

/**
 * 认证远程数据源接口
 */
interface AuthRemoteDataSource {
    suspend fun login(request: LoginRequest): Result<LoginResponse>
}

/**
 * 认证远程数据源实现
 */
class AuthRemoteDataSourceImpl(
    private val authApi: AuthApi
) : AuthRemoteDataSource {
    
    companion object {
        private const val TAG = "AuthRemoteDataSource"
    }
    
    override suspend fun login(request: LoginRequest): Result<LoginResponse> {
        return try {
            val response = authApi.login(request)
            
            if (!response.isSuccessful) {
                val errorMsg = response.message() ?: "登录失败"
                Timber.w("$TAG - login: HTTP请求失败，code=${response.code()}")
                return Result.failure(Exception(errorMsg))
            }
            
            val commonResult = response.body() 
                ?: return Result.failure(Exception("响应体为空"))
            
            if (commonResult.code != ApiConstants.SUCCESS_CODE) {
                val errorMsg = commonResult.msg ?: "登录失败 (code: ${commonResult.code})"
                Timber.w("$TAG - login: 业务失败，code=${commonResult.code}")
                return Result.failure(Exception(errorMsg))
            }
            
            val loginResponse = commonResult.data 
                ?: return Result.failure(Exception("服务器返回数据为空"))
            
            Timber.d("$TAG - login: 登录成功")
            Result.success(loginResponse)
            
        } catch (e: Exception) {
            Timber.e(e, "$TAG - login: 登录异常")
            Result.failure(e)
        }
    }
}
```

#### AuthLocalDataSource.kt

```kotlin
package com.narutohuo.xindazhou.user.data.local

import com.narutohuo.xindazhou.user.data.model.LoginResponse

/**
 * 认证本地数据源接口
 */
interface AuthLocalDataSource {
    suspend fun saveToken(loginResponse: LoginResponse)
    suspend fun getToken(): String?
    suspend fun clearToken()
}

/**
 * 认证本地数据源实现
 */
class AuthLocalDataSourceImpl : AuthLocalDataSource {
    
    override suspend fun saveToken(loginResponse: LoginResponse) {
        TokenManager.saveToken(
            loginResponse.accessToken,
            loginResponse.refreshToken,
            loginResponse.userId
        )
    }
    
    override suspend fun getToken(): String? {
        return TokenManager.getAccessToken()
    }
    
    override suspend fun clearToken() {
        TokenManager.clearToken()
    }
}
```

---

## 六、依赖关系

### 6.1 依赖方向

```
UI 层
  ↓ 依赖
Domain 层
  ↑ 被依赖
Repository 层
  ↓ 依赖
Data 层
```

**原则：**
- 内层不依赖外层
- 外层可以依赖内层
- Domain 层不依赖任何 Android 框架

### 6.2 依赖注入

建议使用依赖注入框架（如 Hilt/Koin）管理依赖关系：

```kotlin
// 示例：使用 Koin
val authModule = module {
    // Data 层
    factory<AuthRemoteDataSource> { AuthRemoteDataSourceImpl(get()) }
    factory<AuthLocalDataSource> { AuthLocalDataSourceImpl() }
    single { AuthApi.create() }
    
    // Repository 层
    single<AuthRepository> { 
        AuthRepository(get(), get()) 
    }
    
    // Domain 层
    factory { LoginUseCase(get()) }
    factory { RegisterUseCase(get()) }
    
    // UI 层
    viewModel { LoginViewModel(get(), get()) }
}
```

---

## 七、最佳实践

### 7.1 状态管理

使用 Sealed Class 定义 UI 状态：

```kotlin
sealed class LoginState {
    object Idle : LoginState()
    object Loading : LoginState()
    data class Success(val message: String) : LoginState()
    data class Error(val message: String) : LoginState()
}
```

### 7.2 错误处理

统一使用 Result 类型处理错误：

```kotlin
suspend fun login(mobile: String, password: String): Result<LoginResponse> {
    return try {
        // 成功
        Result.success(data)
    } catch (e: Exception) {
        // 失败
        Result.failure(e)
    }
}
```

### 7.3 数据转换

Repository 层负责 Data Model 到 Domain Model 的转换：

```kotlin
// Data Model (API 响应)
data class LoginResponseData(...)

// Domain Model (业务模型)
data class User(...)

// Repository 中转换
fun toDomainModel(data: LoginResponseData): User {
    return User(
        id = data.userId,
        name = data.username,
        // ...
    )
}
```

### 7.4 测试建议

- **UI 层**：使用 AndroidX Test 测试 Fragment/Activity
- **ViewModel**：使用 JUnit 测试，不依赖 Android 框架
- **UseCase**：使用 JUnit 测试，Mock Repository
- **Repository**：使用 JUnit 测试，Mock DataSource
- **DataSource**：使用 JUnit 测试，Mock API

### 7.5 命名规范

- **ViewModel**：`功能ViewModel`，如 `LoginViewModel`
- **UseCase**：`功能UseCase`，如 `LoginUseCase`
- **Repository**：`功能Repository`，如 `AuthRepository`
- **DataSource**：`功能RemoteDataSource` / `功能LocalDataSource`
- **State**：`功能State`，如 `LoginState`

---

## 附录：架构检查清单

### 代码审查检查项

- [ ] UI 层是否只包含 UI 逻辑，业务逻辑是否在 UseCase 中
- [ ] ViewModel 是否只调用 UseCase，不直接调用 Repository
- [ ] UseCase 是否封装了业务逻辑验证
- [ ] Repository 是否统一管理数据源（Remote + Local）
- [ ] DataSource 是否分离为 Remote 和 Local
- [ ] 是否遵循依赖方向（内层不依赖外层）
- [ ] 是否使用了 Result 类型处理错误
- [ ] 是否使用 Sealed Class 管理 UI 状态
- [ ] 命名是否规范（ViewModel、UseCase、Repository、DataSource）
- [ ] 是否添加了必要的注释（类、方法）

---

**文档版本**: v1.0  
**最后更新**: 2024-01-01  
**维护者**: 开发团队

