← Назад к вопросам

В чем разница между MVP, MVVM и MVI?

1.8 Middle🔥 171 комментариев
#Архитектура и паттерны

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

MVP, MVVM и MVI: архитектурные паттерны для Android

Эти три паттерна решают одну задачу: отделить бизнес-логику от UI, чтобы код был модульным, тестируемым и поддерживаемым. Но они делают это разными способами. Выбор паттерна влияет на структуру всего приложения.

MVP (Model-View-Presenter)

Архитектура:

View (Activity/Fragment)
    ↕ (callback)
Presenter
    ↕ (CRUD)
Model (Repository, Database)

Как работает:

// View (UI層)
class LoginActivity : AppCompatActivity(), LoginView {
    private lateinit var presenter: LoginPresenter
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        presenter = LoginPresenter(this, UserRepository())
    }
    
    override fun onLoginClicked(email: String, password: String) {
        presenter.login(email, password)
    }
    
    override fun showSuccess() {
        Toast.makeText(this, "Успешно!", Toast.LENGTH_SHORT).show()
    }
}

// View Interface
interface LoginView {
    fun showSuccess()
    fun showError(message: String)
}

// Presenter (бизнес-логика)
class LoginPresenter(val view: LoginView, val repository: UserRepository) {
    fun login(email: String, password: String) {
        repository.login(email, password) { result ->
            if (result.isSuccess) {
                view.showSuccess()
            } else {
                view.showError(result.error)
            }
        }
    }
}

Плюсы:

  • Presenter полностью изолирован от Android Framework
  • Легко тестировать (mock View)
  • Логика отделена от UI

Минусы:

  • Много boilerplate-кода (интерфейсы для каждого View)
  • View и Presenter тесно связаны через interface
  • Presenter держит ссылку на View → риск memory leak если View уничтожена
  • Контракт между View и Presenter нужно вручную поддерживать

MVVM (Model-View-ViewModel)

Архитектура:

View (Activity/Fragment)
    ↓ (observes)
ViewModel (LiveData/StateFlow)
    ↓ (CRUD)
Model (Repository, Database)

Как работает:

// ViewModel — знает о UI, но не знает конкретный View
class LoginViewModel(
    private val repository: UserRepository
) : ViewModel() {
    
    private val _uiState = MutableLiveData<LoginUiState>()
    val uiState: LiveData<LoginUiState> = _uiState
    
    fun login(email: String, password: String) {
        viewModelScope.launch {
            _uiState.value = LoginUiState.Loading
            try {
                val user = repository.login(email, password)
                _uiState.value = LoginUiState.Success(user)
            } catch (e: Exception) {
                _uiState.value = LoginUiState.Error(e.message ?: "")
            }
        }
    }
}

// View (Activity/Fragment)
class LoginActivity : AppCompatActivity() {
    private val viewModel: LoginViewModel by viewModels()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        viewModel.uiState.observe(this) { state ->
            when (state) {
                is LoginUiState.Loading -> showLoading()
                is LoginUiState.Success -> showSuccess(state.user)
                is LoginUiState.Error -> showError(state.message)
            }
        }
    }
}

// State classes
sealed class LoginUiState {
    object Loading : LoginUiState()
    data class Success(val user: User) : LoginUiState()
    data class Error(val message: String) : LoginUiState()
}

Плюсы:

  • Lifecycle-aware: ViewModel выживает при поворотах экрана
  • Нет утечек памяти (View может быть GC-ирована)
  • Меньше boilerplate-кода
  • Two-way binding упрощает синхронизацию
  • Государство сохраняется при пересоздании View

Минусы:

  • ViewModel может стать "God Object" с множеством состояний
  • Сложнее с complex UI logic
  • StateFlow/LiveData требуют понимания reactive patterns

MVI (Model-View-Intent)

Архитектура:

View (Activity/Fragment)
    ↓ (emits Intent/Action)
Intent/Action (User actions)
    ↓
Presenter/ViewModel
    ↓ (produces State)
State
    ↓
View (re-renders)

Как работает:

// Intent — все возможные действия пользователя
sealed class LoginIntent {
    data class LoginClicked(val email: String, val password: String) : LoginIntent()
    object RetryClicked : LoginIntent()
}

// State — полное состояние UI
data class LoginState(
    val isLoading: Boolean = false,
    val user: User? = null,
    val error: String? = null
)

// Reducer — преобразует Intent + State → новый State
class LoginPresenter(private val repository: UserRepository) {
    fun reduce(state: LoginState, intent: LoginIntent): LoginState {
        return when (intent) {
            is LoginIntent.LoginClicked -> {
                // Запустить загрузку
                state.copy(isLoading = true)
            }
            is LoginIntent.RetryClicked -> {
                state.copy(isLoading = true)
            }
        }
    }
    
    fun processIntent(state: LoginState, intent: LoginIntent): Flow<LoginState> = flow {
        when (intent) {
            is LoginIntent.LoginClicked -> {
                emit(state.copy(isLoading = true))
                try {
                    val user = repository.login(intent.email, intent.password)
                    emit(state.copy(isLoading = false, user = user, error = null))
                } catch (e: Exception) {
                    emit(state.copy(isLoading = false, error = e.message))
                }
            }
            is LoginIntent.RetryClicked -> {
                // retry logic
            }
        }
    }
}

// View
class LoginActivity : AppCompatActivity() {
    private val presenter = LoginPresenter(repository)
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        loginButton.setOnClickListener {
            val intent = LoginIntent.LoginClicked(email.text.toString(), password.text.toString())
            presenter.processIntent(currentState, intent).collect { newState ->
                render(newState)
            }
        }
    }
    
    private fun render(state: LoginState) {
        when {
            state.isLoading -> showLoading()
            state.user != null -> showSuccess(state.user)
            state.error != null -> showError(state.error)
        }
    }
}

Плюсы:

  • Однонаправленный поток данных (unidirectional)
  • State полностью описывает UI
  • Легко отследить причину изменений (Intent)
  • Сидеал для Redux-like архитектур
  • Предсказуемое поведение
  • Time-travel debugging (можно воспроизвести состояния)

Минусы:

  • Много boilerplate (Intent, State, Reducer)
  • Кривая обучения выше
  • Может быть overkill для простых экранов
  • Требует RxJava/Flow для реактивности

Сравнительная таблица

КритерийMVPMVVMMVI
СвязностьСредняяНизкаяОчень низкая
BoilerplateВысокийСреднийВысокий
ТестируемостьОтличнаяХорошаяОтличная
ScalabilityСредняяХорошаяОтличная
Для новичкаСложноватоЛегчеСложно
State ManagementManualLiveData/FlowExplicit
Memory LeaksВозможныНетНет

Рекомендации для Android

MVVM — самый популярный выбор для большинства проектов:

  • Официально поддерживается Google
  • Хороший баланс между простотой и контролем
  • Lifecycle-aware, no memory leaks
  • Хорошо работает с Room, Retrofit, Coroutines

MVI — для сложных, high-load приложений:

  • Redux-like, очень предсказуемо
  • Лучше для масштабных команд
  • Требует Redux/Mobius/Elm-like библиотек

MVP — legacy паттерн, в новых проектах используется редко.

Выбор паттерна зависит от сложности приложения, опыта команды и требований к масштабируемости.

В чем разница между MVP, MVVM и MVI? | PrepBro