Что такое паттерн MVP (Model-View-Presenter)?

«Что такое паттерн MVP (Model-View-Presenter)?» — вопрос из категории Архитектура, который задают на 10% собеседований IOS Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

MVP (Model-View-Presenter) — это архитектурный паттерн, разделяющий ответственность в приложении на три компонента для улучшения тестируемости и разделения кода.

Компоненты:

  1. Model — содержит бизнес-логику и данные приложения.
  2. View — пассивный интерфейс, который только отображает данные и передает пользовательские события Presenter'у. Обычно реализуется UIViewController или UIView в iOS.
  3. Presenter — посредник между Model и View. Он получает события от View, обрабатывает их (используя Model), подготавливает данные и обновляет View.

Ключевое отличие от MVC: View в MVP пассивна и ничего не знает о Model. Вся логика отображения и обработки событий перемещена в Presenter, что разрывает прямую связь между View и Model.

Пример на Swift:

// 1. Протокол для View (интерфейс)
protocol LoginViewProtocol: AnyObject {
    func showLoading(_ isLoading: Bool)
    func showError(message: String)
    func navigateToHome()
}

// 2. Presenter
class LoginPresenter {
    weak var view: LoginViewProtocol?
    private let authService: AuthService // Model-слой

    func didTapLoginButton(username: String, password: String) {
        view?.showLoading(true)
        authService.login(username: username, password: password) { [weak self] result in
            self?.view?.showLoading(false)
            switch result {
            case .success:
                self?.view?.navigateToHome()
            case .failure(let error):
                self?.view?.showError(message: error.localizedDescription)
            }
        }
    }
}

// 3. View (ViewController)
class LoginViewController: UIViewController, LoginViewProtocol {
    var presenter: LoginPresenter!
    @IBOutlet weak var usernameTextField: UITextField!
    @IBOutlet weak var passwordTextField: UITextField!

    @IBAction func loginButtonTapped(_ sender: Any) {
        presenter.didTapLoginButton(username: usernameTextField.text ?? "",
                                    password: passwordTextField.text ?? "")
    }

    // Реализация протокола LoginViewProtocol
    func showLoading(_ isLoading: Bool) { /* ... */ }
    func showError(message: String) { /* ... */ }
    func navigateToHome() { /* ... */ }
}

Преимущества: Высокая тестируемость Presenter'а (без UIKit), четкое разделение ответственности.