前言

MVC 和 MVVM 是比较常用的前端设计模式,你能清楚地说出 MVVM 到底比 MVC 好在哪里吗?我最近了解了一下 SwiftUI 里的 MVVM 是怎么应用的,觉得应该记录一下自己的想法,对这两种模式的对比和优缺点做个总结。

MVC

先来回顾下 MVC 和 MVVM 各自的特征。前端的 MVC 如下所示:

三个抽象角色,Model 代表模型,View 代表视图,Controller 代表控制器。三个角色间的通信如下:

  1. View 接收到用户的交互请求之后,会将请求转发给 Controller。
  2. Controller 解析用户的请求之后,就会交给对应的 Model 去处理。
  3. Model 处理完后会通知 View。

这里要重点关注下 MVC 中数据变化是如何响应到界面上的:Model 和 View 之间使用了观察者模式,以便在 Model 变化时去响应界面的变化,也就是说 View 是依赖 Model 的。

MVVM

MVVM 中也有三个角色,Model 代表模型,View 代表视图,ViewModel 代表视图模型。通信的步骤如下:

  1. View 把用户请求传送给 ViewModel。
  2. ViewModel 通知 Model 去处理。
  3. Model 处理完后通知 ViewModel 需要更新。
  4. ViewModel 通知 View 需要更新,View 从 ViewModel 中取得需要的数据。

我们应该注意到 MVVM 跟 MVC 有一些不同:View 和 Model 没有直接的依赖关系,并且 ViewModel 会承担一定的显示逻辑处理。

MVVM 好在哪里?

在 MVC 中,主要有两个缺点:

  1. 当 Model 发生变化时 View 会收到通知以便取得需要的数据,这意味着 View 可以直接访问 Model,也就是 View 耦合了 Model。那么当 Model 发生变化时,所有相关的 View 都会受到影响。
  2. 因为 Model 中不包含显示逻辑,所以不可避免地使 View 中会包含一定的业务显示逻辑,造成 View 的职责不清和可能的代码重复。

以上的缺点在 MVVM 中都得到了改进。第一,View 只关联 ViewModel,降低了耦合性;其次,View 可以关联多个 ViewModel 来组合界面的显示,相关联 Model 的业务显示逻辑代码可以抽离到 ViewModel 中,提高了代码可重用性和灵活性,并使 View 更纯粹;最后,View 和 ViewModel 通常使用双向数据绑定来简化开发者的代码编写。

MVVM 在 SwiftUI 里怎么用的

来看个 MVVM 的实际例子。SwiftUI 是通过继承和属性包装器来实现 MVVM 的应用场景的,代码写起来非常简单:

import SwiftUI

/**
 * ViewModel 层。
 * ViewModel 是可观察的对象,继承自 ObservableObject,观察者是 View。
 */
class ViewModel: ObservableObject {
  typealias Card = MemoryGame<String>.Card
  
  static private let emojis = ["🚗", "🚕", "🚙", "🚌", "🚎", "🚝", "✈️", "🚀"]
  
  static private func createMemoryGame() -> MemoryGame<String> {
    MemoryGame<String>(numberOfPairsOfCards: 4) { idx in
      emojis[idx]
    }
  }
  
  /**
   * ViewModel 引用了 Model。
   * model 属性是 Model 层,注意有个 @Published 属性包装器。
   * @Published 的作用是当 model 发生变化时,ViewModel 发布通知去通知观察者,也就是 View。
   * 这里简化了 Model 通知 ViewModel,ViewModel 再通知 View 的过程。
   */
  @Published private var model = createMemoryGame()
  
  /**
   * 以下体现的是 ViewModel 代理了对于 Model 的访问。
   * 一些显示逻辑代码可以抽离到这里。
   */
  var cards: Array<Card> {
    return model.cards
  }
  
  func choose(_ card: Card) {
    model.choose(card)
  }
}
import SwiftUI

struct EmojiMemoryGameView: View {
  /**
   * @ObservedObject 属性包装器注册了 View 和 ViewModel 的观察关系。
   * 当 ViewModel 发生变化时 View 会重新渲染界面。
   */
  @ObservedObject var vm: EmojiMemoryGame
  
  var body: some View {
    // 显示的界面。
  }
}

总结

通过以上的描述应该可以清楚地回答出 MVVM 比 MVC 到底好在哪里了。并且可以发现不同的 UI 框架在 MVVM 的使用上可能各有侧重点,比如童鞋们可以把 Vue 的 MVVM 与 SwiftUI 做个对比。


deepfunc
771 声望634 粉丝