介绍

这一次,我将通过一个简单的例子来介绍 MVP。
还有一篇关于MVP形象的文章,喜欢的话请继续。

假定读者

  • MVP 听说过但不太了解的人
  • 那些正在使用 MVC 开发但想要解决代码可见性差的问题的人

示例应用

这次我做的介绍MVP的app是一个简单的除法app。

功能细节

  • 输入除数和除数查看计算结果
  • 如果除数为 0,则显示错误

* 输入字符不是数字时的细节处理非常恰当。

【Swift】MVPを簡単なサンプルと共に。

模型、视图和演示者的角色

主持人

主持人的工作

  • 从 View 接收事件

    • 视图已加载
    • 点击了一个按钮
  • 从 View 接收事件并请求处理到 Model

    • 从服务器获取值
    • 电子邮件验证
  • 根据Model返回的处理结果,确定并指示在View中显示什么

    • 显示计算结果
    • 显示错误

演示者的特点

  • import UIKit 没有。

  • 我不知道如何显示这样的东西: (View工作)

    • 在 CollectionView 中显示?
    • 在标签中显示?
    • 在警报中显示?
  • 我不知道如何做类似下面的事情。 (Model工作)

    • 从服务器获取价值?
    • 从 UserDefault 获取值?

如上,Presenter 不知道细节。但我知道这个过程是如何运作的。
换句话说,它就像规格表一样工作。你什么时候做什么你问谁?可以在列表中看到的图像。
查看 Presenter 可以更轻松地了解屏幕是哪种处理流程。
反过来说,您应该编写流程并为其命名,以便您在查看 Presenter 时可以理解流程。

演示者代码

这是演示者代码。
自从使用 MVP 实现它以来,通过查看 Presenter 来了解一般处理流程的时间变得非常快。

import Foundation

// InputにはViewControllerからどんなイベントを受け取るかを書きます
// 今回のサンプルではボタンをタップされたときに値を受け取って、Modelに渡したい
// Inputの命名はViewでどんなイベントが起こったかを関数名にすると分かりやすくなります
// 例)画面が読み込まれた時に何かの処理が必要ならViewDidLoadというメソッドを作るといいでしょう
protocol PresenterInput: AnyObject {
    func tappedButton(dividend: Int, divisor: Int)
    // 例)func viewDidLoad()
}

// OutputにはModelから処理の結果を受け取ったのち、何を表示してほしいかを書きます
// 今回は計算結果を表示するメソッド、割る数が0でエラーを表示するメソッドを実装しています
protocol PresenterOutput: AnyObject {
    func showResult(quotient: Int)
    func showError(error: DivisionError)
}

final class Presenter {

    weak var view: PresenterOutput?
    private let model: DivisionModelInput

    // このへん何をしているんだろうという人は「依存性注入(DI)」を調べてみてください。
    init(view: PresenterOutput, model: DivisionModelInput) {
        self.view = view
        self.model = model
    }
}

extension Presenter: PresenterInput {

    // ButtonをタップされたときにModelに処理を依頼します
    // 返ってきた値を元にViewに何を表示してほしいかを指示します
    func tappedButton(dividend: Int, divisor: Int) {
        model.fetchQuotient(dividend: dividend, divisor: divisor) { [weak self] result in
            guard let strongSelf = self else { return }
            switch result {
            case .success(let quotient):
                strongSelf.view?.showResult(quotient: quotient)
            case .failure(let error):
                strongSelf.view?.showError(error: error)
            }
        }
    }
}

看法

视图的作用

  • 将视图中发生的事件传达给演示者
  • 显示 Presenter 让您显示的内容
  • 确定要显示的 UI 部分

视图的特点

  • import UIKit
  • 将事件通知给 Presenter 后我不知道该怎么办
  • 视图会说什么?我不评判诸如此类的事情
  • 无论如何都显示演示者所说的任何内容
  • 不要直接与模型交互

示例代码

import UIKit

final class ViewController: UIViewController {

    @IBOutlet private weak var dividendTextField: UITextField!
    @IBOutlet private weak var divisorTextField: UITextField!
    @IBOutlet private weak var resultLabel: UILabel!

    @IBAction private func tappedButton(_ sender: UIButton) {
        let dividend = Int(dividendTextField.text!)!
        let divisor = Int(divisorTextField.text!)!
        presenter.tappedButton(dividend: dividend, divisor: divisor)
    }

    private var presenter: PresenterInput!

    override func viewDidLoad() {
        super.viewDidLoad()
        presenter = Presenter(view: self, model: DivisionModel())
    }
}

extension ViewController: PresenterOutput {

    func showResult(quotient: Int) {
        resultLabel.text = String(quotient)
    }

    func showError(error: DivisionError) {
        switch error {
        case .dividendByZero:
            let alert = UIAlertController(title: "エラー", message: "0では割れません", preferredStyle: .alert)
            let ok = UIAlertAction(title: "はい", style: .default, handler: nil)
            alert.addAction(ok)
            present(alert, animated: true, completion: nil)
        }
    }
}

模型

模型的作用

  • 执行Presenter请求的处理并返回结果
  • 扮演除演示者和查看者之外的角色
    • 与服务器通信
    • 验证密码设置功能中使用的字符

型号特点

  • import UIKit 没有
  • 除了 Presenter 和 View 之外的所有内容,因此覆盖范围非常广泛
  • 与演示者交互
  • 与 View 没有直接交互

示例代码

有没有感觉 Model 没有特别的转折?
由于它被设置为在服务器上处理和接收,我尝试用闭包返回 Result。

import Foundation

enum DivisionError: Error {
    case dividendByZero
}

protocol DivisionModelInput {
    func fetchQuotient(dividend: Int, divisor: Int, completion: @escaping (Result<Int, DivisionError>) -> Void)
}

final class DivisionModel: DivisionModelInput {

    // サーバーと通信して値を取得するということを想定してクロージャを使用しました。
    func fetchQuotient(dividend: Int, divisor: Int, completion: @escaping (Result<Int, DivisionError>) -> Void) {

        if divisor == 0 {
            completion(.failure(.dividendByZero))
            return
        }

        let quotient = dividend / divisor
        completion(.success(quotient))
    }
}

加阿尔法

MVP 面临的挑战

使用 MVP 开发时,最大的障碍是如何处理模型部分。要了解如何做到这一点,我们需要学习系统架构,例如干净架构和 VIPER。顺便说一句,MVP、MVC 和 MVVM 被称为 GUI 架构。

MVP 中的协议

我在 Presenter 中使用了两个协议:输入和输出。
如果你看其他文章,你会发现它经常是这样写的。但是,我不认为协议对于 Presenter 来说总是必要的。我认为关键是将 Presentation 逻辑与 ViewController 分开。

综上所述

它曾是怎样的?如果我能更接近于理解 MVP,我会很高兴。
另外,如果我的理解有误,请告诉我!
好吧!


原创声明:本文系作者授权爱码网发表,未经许可,不得转载;

原文地址:https://www.likecs.com/show-308623269.html

相关文章: