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

Ответ

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), четкое разделение ответственности.

Ответ 18+ 🔞

Давай я тебе про этот MVP разжую, а то смотрю, народ путается, как хуй с пальцем. Архитектурный паттерн, блядь, звучит страшно, а на деле всё просто, как три копейки.

Представь себе такую картину. У тебя есть Модель (Model) — это типа наш молчаливый качок, Герасим. Он нихуя не говорит, но всю реальную работу делает. Данные там держит, с сетью общается, бизнес-логику ебашит. Молчит и пашет.

Потом у нас Вью (View) — это наша барышня-истеричка, которая только умеет кричать «ой, кнопку нажали!» или «ой, покажи мне вот эти цифры красиво!». Сама она нихуя не решает, просто интерфейс, лицо приложения. В iOS это обычно наш UIViewController, который превратился из бога в послушную куклу.

И наконец, Презентер (Presenter) — это главный режиссер-постановщик, ебаный мозг всего этого цирка. Он стоит посередине. Вью ему: «Чувак, юзер логин ввел и на кнопку ебнул!». Презентер такой: «Окей, заткнись, я всё решу». Идёт к молчаливому Герасиму (Модели): «Братан, проверь логин/пароль». Получает ответ и командует Вью: «Всё ок, включай крутилку!» или «Э, сука, ошибка, покажи алерт!».

В чём соль, спросишь? А в том, что в классическом MVC наша Вьюха (ViewController) была и режиссёром, и актёром, и грузчиком — пиздец бардак, протестировать ничего нельзя. А тут мы Вьюху опустили до уровня тупой отрисовки. Вся логика, вся «думалка» теперь в Презентере, который нихуя не знает про UIKit и его можно нахуй протестировать юнит-тестами, как миленького. View и Model друг про друга не знают вообще — красота!

Смотри, как это в коде выглядит, без всякой ерунды:

// 1. Это наш контракт для Вьюхи. Чёткий список: «Чего ты от меня хочешь, Презентер?»
protocol LoginViewProtocol: AnyObject {
    func showLoading(_ isLoading: Bool) // Покажи крутилку, сука
    func showError(message: String) // Ошибку высрала, покажи
    func navigateToHome() // Всё чики-пуки, иди на след. экран
}

// 2. А вот и наш царь и бог — Презентер
class LoginPresenter {
    weak var view: LoginViewProtocol? // Он знает только про интерфейс Вьюхи, а не про всю её потрошную
    private let authService: AuthService // А это наш Герасим (Модель)

    // Сюда прилетает событие от Вьюхи
    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. И наша Вьюха-кукла (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 ?? "")
    }

    // Далее она просто тупо выполняет команды Презентера, как обученная обезьянка
    func showLoading(_ isLoading: Bool) { /* Включаем/выключаем крутилку */ }
    func showError(message: String) { /* Показываем алерт с текстом */ }
    func navigateToHome() { /* Переходим куда надо */ }
}

Итог, ёпта? Тестируемость — овердохуищная, потому что Презентер — это просто обычный класс, можно его отвязать от Вьюхи и тестировать логику в изоляции. Ответственность разделена — каждый делает своё, не лезет в чужой монастырь. Красота, да и только! Главное — не давай Вьюхе задумываться, а то опять в старый бардак скатишься.