MVVM和VIPER之间的界线模糊

最初发表在 Swift Post上

如果您开发移动应用程序已有一段时间,那么您可能听说过MVVM和VIPER。 虽然有些人说MVVM不能很好地扩展,但另一些人则说VIPER太过分了。 我在这里解释为什么我认为它们非常相似,我们甚至不需要将两者分开。

首先让我们快速了解一下MVVM和VIPER。

  • View提供用户操作以查看模型
  • 视图模型处理这些操作并更新其状态。
  • 然后, 视图模型通过数据绑定或使用委派或块手动通知视图
  • 视图将用户操作传递给演示者
  • 然后, 演示者将这些动作传递给交互器路由器
  • 如果该动作需要计算,则交互器将其处理并将状态返回给presenter
  • 演示者然后将此状态转换为演示数据并更新视图
  • 导航逻辑封装在路由器中演示者负责触发它。

有关这些架构的更多信息,请参考 Bohdan Orlov的 这篇精彩文章 iOS Architecture Patterns

我们的首要目标是将UI和业务逻辑分开。 因此,我们可以轻松更新UI而不破坏任何业务规则,也可以隔离测试业务逻辑。 MVVM和VIPER都保证了这一点,但是方式不同。 如果从这个角度来看,它们的结构如下。

MVVM在UI层中只有一个视图组件,而VIPER将UI职责分为三个组件,即View,Presenter和Router。 同样很明显,业务层看起来几乎相同。

让我们看看它们在实践中在UI层中实际上有何不同。

想象一下,我们正在使用MVVM构建一个简单的应用程序,该应用程序将从IMDB中获取前25部电影并将其显示在列表中。 这些组件可能如下所示。

几周后,苹果发布了iOS 26,乔尼·艾夫(Jony Ive)通过引入全新的设计系统,拉开了一个新招。 我们的设计师兴奋不已,并迅速提出了适用于iOS的新UI。 现在,我们的工作将是实施此新设计并通过A / B测试证明其可用性,这意味着我们将仅向一定比例的用户显示新UI。

实际上,我们的信誉很好! 我们只需要为iOS创建一个符合MovieListView协议的新视图,并将其与演示者连接即可。 十分简单。

 protocol MovieListView: MovieListPresenterDelegate { 
...
func didTapOnMovie(at index: Int)
func showDetailView(for movie: Movie)
}

在实现此新类时,我们将意识到showDetailView实现在旧视图和新视图中都应相同。 首先,我们可能会从旧类中复制粘贴此部分,但随后它可能会杀死我们内部,因为我们都知道复制粘贴是不好的。

好的-我们也将其删除。 我们再次将这个组件称为路由器。

 protocol MovieListRouter { 
func showDetailView(for movie: Movie)
}

该路由器将弱引用当前视图,并负责在需要时推送详细视图。 但是,我们在哪里保留此组件? 我们应该在新旧视图类中编写它吗? 我们可以……但是,从经验上讲,观点最好是愚蠢的,因为它们往往会发生很大变化。

让我们也将此责任推给演示者,让它构成路由器。 这种方式视图仅负责将用户操作传递给演示者。 然后,演示者将决定何时调用视图模型进行计算,或何时调用路由器以进行导航。

现在,我们还重用了导航逻辑,因此可以放心释放。

让我们看一下最终的结构。

我想您现在明白了。 如果将MovieListViewModel重命名为MovieListInteractor ,则我们是100%VIPER,并且没有违反MVVM强制执行的任何规则。

软件体系结构只是一组规则。 有些拥有更少的规则,有些拥有更多的规则。 使用一个不一定代表放弃另一个。 的确如此,尤其是当我们谈论MVC,MVVM和VIPER时。

 MVC 📗 → MVVM 📘 → VIPER 📙 

从左到右,我只看到一个实现可伸缩性而不是矛盾的演进过程。 VIPER只是其中所有内容中最详细的版本,因此有些人认为它是一个过大的杀手。 听这些人的话,他们可能是对的!

VIPER完全包含5个组件,您可能在所有情况下都不需要全部。 我认为我们应该在开发功能时运用最佳判断力,而不是一味地接受一些虚构的规则。

我当前的开发秘诀如下:

  • 从VIPER-lite(与MVVM完全相同)开始。 初学者只能使用视图,交互器和实体。
  • 如果您期望UI会很快改变,请在您的实现中附加一个演示者。
  • 如果您具有复杂/可重复使用的路由逻辑,请附加路由器。
  • 对于每个功能,都有一个单独的文件来布局类接口,并在实际实现之前设计它们之间的通信。 通常认为这是一项开销,但它绝对可以帮助您设计更好的接口,最终可以节省时间。

注意事项

  • 您不能在协议中定义私有变量,它们仅用于提供有关预期实现的提示。
  • 诸如didTapOnMovie(at:)presentation(from:)类的某些方法在实际实现中也将是私有方法。 出于演示目的,它们被显示为协议的一部分。

你怎么看? 如果您有任何疑问或反馈,请在下面发表评论。 💬

谢谢阅读!

帮助传播信息。 ❤️👏