Назад к вопросам
Middle
349
questionbank

В чем отличие между MVM и MVP?

Sobes Copilot

Получайте ответы в реальном времени

Ответ от нейросети

sobes.tech AI

MVM (Model-View-ViewModel) и MVP (Model-View-Presenter) — паттерны проектирования пользовательского интерфейса.

Отличия:

  • Связывание:
    • MVM использует двустороннее связывание данных (data binding) между View и ViewModel. Изменения в ViewModel автоматически отображаются в View, и наоборот.
    • MVP обычно использует одностороннее связывание: Presenter обновляет View, а View уведомляет Presenter о событиях.
  • Ответственность View:
    • В MVM View — "глупая" (passive), ее основная задача — отображение данных из ViewModel и отправка пользовательских действий обратно в ViewModel. Логика представления полностью находится в ViewModel.
    • В MVP View — также "глупая", но Presenter полностью управляет ее обновлением и реакцией на события. View информирует Presenter о событиях, и Presenter решает, как на них отреагировать и как обновить View.
  • Тестируемость:
    • ViewModel в MVM легко тестируется, так как не зависит от UIKit/AppKit.
    • Presenter в MVP также хорошо тестируется, но его зависимость от абстрактного интерфейса View может требовать мокирования.
  • Зависимости:
    • MVM: View зависит от ViewModel, ViewModel зависит от Model.
    • MVP: View зависит от Presenter, Presenter зависит от Model. View и Presenter связаны интерфейсами.
  • Координация:
    • В MVM View вызывает команды (commands) или методы в ViewModel в ответ на пользовательские действия.
    • В MVP View вызывает методы в Presenter через интерфейс, а Presenter вызывает методы для обновления View также через интерфейс.

Пример MVM (схематично):

// Model
struct User {
    let name: String
}

// ViewModel
class UserViewModel { // ObservableObject в SwiftUI или KVO/Reactive в UIKit
    @Published var userName: String = "" // Или Observable в Reactive frameworks

    private var user: User?

    func loadUser() {
        // Загрузка данных из Model
        user = User(name: "John Doe")
        userName = user?.name ?? ""
    }

    func changeName(newName: String) {
        // Обработка логики и обновление Model (при необходимости)
        // user?.name = newName // Если Model мутабельна
        userName = newName
    }
}

// View (SwiftUI)
struct UserView: View {
    @StateObject var viewModel = UserViewModel()

    var body: some View {
        VStack {
            Text(viewModel.userName) // Binding
            Button("Load User") {
                viewModel.loadUser() // Action binding
            }
            TextField("New Name", text: $viewModel.userName) // Two-way binding
        }
        .onAppear {
            viewModel.loadUser() // Пример вызова при появлении
        }
    }
}

Пример MVP (схематично):

// Model
struct User {
    let name: String
}

// View Protocol
protocol UserViewProtocol: AnyObject {
    func display(userName: String)
    func showLoadingIndicator()
    func hideLoadingIndicator()
}

// Presenter
class UserPresenter {
    private weak var view: UserViewProtocol?
    private var user: User?

    init(view: UserViewProtocol) {
        self.view = view
    }

    func viewDidLoad() {
        view?.showLoadingIndicator()
        // Асинхронная загрузка данных из Model
        DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
            self.user = User(name: "Jane Doe")
            self.view?.hideLoadingIndicator()
            self.view?.display(userName: self.user?.name ?? "")
        }
    }

    func didTapChangeNameButton(newName: String) {
        // Обработка логики и обновление Model (при необходимости)
        // user?.name = newName // Если Model мутабельна
        view?.display(userName: newName)
    }
}

// View (UIKit)
class UserViewController: UIViewController, UserViewProtocol {
    @IBOutlet weak var nameLabel: UILabel!
    @IBOutlet weak var loadingIndicator: UIActivityIndicatorView!
    @IBOutlet weak var nameTextField: UITextField!
    private var presenter: UserPresenter!

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter = UserPresenter(view: self)
        presenter.viewDidLoad()
    }

    // MARK: - UserViewProtocol
    func display(userName: String) {
        nameLabel.text = userName
    }

    func showLoadingIndicator() {
        loadingIndicator.startAnimating()
    }

    func hideLoadingIndicator() {
        loadingIndicator.stopAnimating()
    }

    // Trigger presenter actions from UI events
    @IBAction func changeNameButtonTapped(_ sender: UIButton) {
        if let newName = nameTextField.text {
            presenter.didTapChangeNameButton(newName: newName)
        }
    }
}