[译] 使用流动控制器(Flow Controller )实现 MVVM 协议模型

2024-06-02 07:32

本文主要是介绍[译] 使用流动控制器(Flow Controller )实现 MVVM 协议模型,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

  • 原文链接 : MVVM with Flow Controller-First Step
  • 原文作者 : Rodrigo Reis
  • 译文出自 : 掘金翻译计划
  • 译者 : shixinzhang
  • 校对者: yifili09 , rccoder

我看了好久 Krzysztof Zablocki 关于 MVVM 的视频,最后发现理解新东西只有一种方法:动手建个项目!

在阅读许多关于软件架构的知识后,我最近 6 个月一直在学习 MVVM 协议模型。为了理解这个协议需要引用 Natasha The Robot 的一篇文章,这篇文章里介绍了关于编程协议的所有知识。如果你听不到我说的是什么鬼,建议你最好去读一下 Natasha The Robot

一个月前我看完了 Steve “Scotty” Scott 关于 MVVM-C 的课程。在这个我今年看过最佳视频之一的视频中,阐述了最重要的不是代码量减少,而是这个架构能让我们的软件有什么提升。我不反对人们把某个技术称作“银弹”(译者注:“银弹”有“狂拽炫酷吊炸天”一样浮夸的意思),但是我更喜欢追求极致、找到最好的解决方案。

最近几周,我想了很多有关如何提高我对 MVVM 架构的理解,并且创建一个可维护的开发框架。所以我看了 Krzysztof Zabłocki 关于软件架构的视频,
这个视频太赞了。如果你想看讲了什么可以点这里看视频或者点这里看博客。

看完 Krzysztof Zablocki 的视频后我决定建个项目来实现一种更好的架构。所以,我为(实现)这个架构制定了清晰的目标。

总目标

在选择哪一个架构之前,我会制定一个包含这个架构所关注的能解决什么目标的列表,这是从我多年 Java 项目开发中总结出的。这帮助我定义我们架构的优点。下面是促使我测试的要点。

模块

我希望我的架构可以创建代码可用性强的模块。还可以创建整个项目都可以复用的结构,同时能够使用某个方法创建一个灵活的接口,
以至于项目可拓展性比较好。

好,开始测试

单元测试和用户界面测试,这个就不用解释了吧。但我关注的是有关架构的分层,它为了(更好的部署)自动测试,让 QA 分析员想出新的测试机制来保证应用程序的(高)质量。

A/B 测试(简单来说,就是为同一个目标制定两个方案,让一部分用户使用 A 方案,另一部分用户使用 B 方案,记录下用户的使用情况,看哪个方案更符合设计)

应用市场上基于不同的界面和功能的应用日益复杂,界定应用优劣与否的方案有很多。在这里我重点研究应用是否有自定义和模拟用户体验的能力。

MVVM 与流控制器

在这个概念下,我决定将完全使用 MVVM 写接口来创建一个明确的区分。添加必要的依赖关系。管理这些依赖并且决定哪些将使用的接口会是流控制器。

流控制器

流控制器是一个控制用户路径的小型类和结构的集合。这使我们能够为 A / B 测试创建不同的数据流,例如,权限管理。
流之间的通信是通过一个共同的、可以传递窗口引用或导航控制器的对象,那可以让你创造出不同流的导航。

该模型的另一个重要的功能就是它可以负责为 ViewController 实例化并注入 ViewModel + Model。
这有助于依赖注入时代码重用更多。对于这种情况,有必要研究一下 Swift 的泛型,虽然它仍然有一些问题。

MVVM

这种架构和我之前项目的架构很像,唯一不同的是 VC (ViewController) 必须接受一个兼容的 ViewModel(通过既定协议)。
因此 VC 是独立的、封装完整的,重要的是要方便测试和提高代码的重用性。

这种独立意味着在我想要让界面灵活可变的时候可以用这种控制器来实现。另一个例子是抽象相似界面,如网格和列表使用相同的 ViewModel 。抽象必然会更复杂些,但当你的应用程序的增长或者随着时间的变化,你的收益也会越来越多。

我谈论的是保持一个应用持续发展的方法,改进一个成品的代码和创建第一个版本一样重要。
更多细节可以看这篇文章: https://medium.com/@digoreis/your-app-is-getting-old-at-this-time-e025662e20e7#.py9qlarui

在下面的文本中解释了架构测试的原因后,我将举例证明初步的结果。

实战项目

我决定创建一个简单的项目,一个列表和详情。为了便于理解和证明我要测试的另一个很重要的点,不使用 CocoaPods,不能使用依赖。

我注意到一件事,随着时间的推进,我们都意识到开发应用时构建的时间很长,这是因为项目主要几步的编译问题。一开始评估时可能只会看到部分细节,
然而事实是等待 Xcode 翻译、组织项目浪费了许多时间。

digoreis/ExampleMVVMFlow _ExampleMVVMFlow - One Example of MVVM w/ Flow Controller_github.com

Storyboard

我不赞同在 Xcode 中 Storyboard 带走什么是不好的。相反,不使用它的结果才是值得我们担心的。在下个项目中我将考虑不使用它,这只不过是一个本地代码的 XML 表示。在一个项目合并复杂性和构建时间逐渐增长的成熟团队中,我认为每个人都应该思考一下这个。

但请不要争论!

挑战

挑战的第一阶段是很简单的,作为一个项目列表显示他们,并选择一个显示细节。我相信,这是开发应用程序的最常见的任务。在这里是一个简单的猫头鹰列表,有名称,照片和描述。这个内容的显示是通过 FlowController 枚举配置的。

我不会讲太多我决定构建的内容有多混乱,因为我在很短的的时间( 8 小时)内测试我的抽象极限,现在正在完善的代码,而不是增加项目。
在下一节中,我讲讲实验的结果。

结果

第一步是把 Storyboards(左边启动屏的)和其他不会使用的东西去掉。然后只在应用启动时开始系统流程。

import UIKit@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {var window: UIWindow?func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {window = UIWindow(frame : UIScreen.mainScreen().bounds)let configure = FlowConfigure(window: window, navigationController: nil, parent: nil)let mainFlow = MainFlowController(configure: configure)mainFlow.start()return true}
}

在为流程系统提供应用程序窗口后,它启动一个看起来像是下图所示的树的系统。为了使用导航,我想保持 UINavigationController ,
这样你就可以从 UIWindow 或 UINavigationController 启动流。

关于 MVVM 与流控制器的基本方案

一个流初始化时会构建一个 ViewModel 和 Model(需要的话会更多),启动创造了必要的接口的方法,添加它的依赖。
这需要这些实体之间的代码耦合更具优势。
我们可以看到在 OwlsFlowController 案例中,通过配置选择是否在网格还是列表中显示数据,在本例中是固定的,但它可以有两种测试情况。

import UIKitclass OwlsFlowController : FlowController, GridViewControllerDelegate, ListTableViewControllerDelegate {private let showType = ShowType.Listprivate let configure : FlowConfigureprivate let model = OwlModel()private let viewModel : ListViewModel<OwlModel>required init(configure : FlowConfigure) {self.configure = configureviewModel = ListViewModel<OwlModel>(model: model)}func start() {switch showType {case .List:let configureTable = ConfigureTable(styleTable: .Plain, title: "List of Owls",delegate: self)let viewController = ListTableViewController<OwlModel>(viewModel: viewModel, configure: configureTable) { owl, cell incell.textLabel?.text = owl.name}configure.navigationController?.pushViewController(viewController, animated: false)breakcase .Grid:let layoutGrid = UICollectionViewFlowLayout()layoutGrid.scrollDirection = .Verticallet configureGrid = ConfigureGrid(viewLayout: layoutGrid, title: "Grid of Owls", delegate: self)let viewController = GridViewController<OwlModel>(configure: configureGrid) { owl, cell incell.image?.image = owl.avatar}viewController.configure(viewModel:viewModel)configure.navigationController?.pushViewController(viewController, animated: false)break}}private enum ShowType {case Listcase Grid}func openDetail(id : Int) {let detail = FlowConfigure(window: nil, navigationController: configure.navigationController, parent: self)let childFlow = OwlDetailFlowController(configure: detail,item: viewModel.item(ofIndex: id))childFlow.start()}
}

该模型的有点是应用中的大多数列表都共享相同的行为和相同的接口。在本例中,只有数据和子单元的变化,可以作为一个参数传递,并为所有列表创建一份可重用的代码。

这里有趣的一点是实现了两种响应协议:一个用于网格和一个列表。但两个的实现是相同的。这很有趣,因为我对每种类型的接口都有单独的操作,但通用的操作可以共享,同时不使用继承。

import UIKitstruct ConfigureTable {let styleTable : UITableViewStylelet title : Stringlet delegate : ListTableViewControllerDelegate
}protocol ListTableViewControllerDelegate {func openDetail(id : Int)
}class ListTableViewController<M : ListModel>: UITableViewController {var viewModel : ListViewModel<M>var populateCell : (M.Model,UITableViewCell) -> (Void)var configure : ConfigureTableinit(viewModel model : ListViewModel<M>, configure : ConfigureTable, populateCell : (M.Model,UITableViewCell) -> (Void)) {self.viewModel = modelself.populateCell = populateCellself.configure = configuresuper.init(style: configure.styleTable)self.title = configure.title}override func viewDidLoad() {super.viewDidLoad()}override func didReceiveMemoryWarning() {super.didReceiveMemoryWarning()}override func numberOfSectionsInTableView(tableView: UITableView) -> Int {return 1}override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {return viewModel.count()}override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {let cell = UITableViewCell(style: .Default, reuseIdentifier: "Cell")populateCell(viewModel.item(ofIndex: indexPath.row), cell)return cell}override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {configure.delegate.openDetail(indexPath.row)}}

接口的实现是清晰和干净的,它是一个有简单的参数展示的客观的基础设施。所有的创建、删除都没有业务实现。

另一件事是为了填充子单元封闭的通道,在不久将来它可以允许我们用一个参数来决定使用那部手机。这种架构的想法是将接口分为两部分,第一部分是一系列现成的基础设施和可重复使用的整个项目。

第二部分 UIViews 和 子单元为每个情况,对每一个数据集进行定制化。因此,我们通常的测试可以覆盖大多数的接口,增加安全性的实现。

备注:因为某些原因,在某些情况下,Swift 将不会接受一个泛型类型作为一个 init 方法的协议参数。目前仍在调查究竟是 Swift 的 bug 还是故意限制。

得到的结果是代码非常干净,并最大限度地提高接口的重用。还研究了泛型和协议作为一种抽象问题的方法。其他的结果是构建时间明显快得多。

这些都是这几个星期的初步结果,还有其他我期待的结果我会在其他文章中一一介绍。如果他们想在 Github 上跟随或者想在 Medium 上编辑文章,
我将把文章发上去。

接下来要做的事和致谢。

要做的事:

  • 测试:单元测试和模拟界面测试(我开始测试的结果是 78% 的覆盖率)
  • 扩展模型 :其他对象(我需要找到其他的动物)
  • 接口和基础设施:创建其他类型的单元,使用相同的 UIViewController

我的下一篇文章将是如何建立有效的测试,简单易维护。祝我好运吧。

特别致谢:

首先猫头鹰的灵感来自我的妻子。她喜欢猫头鹰。我也需要你感谢 HootSuite 制造了这一系列很酷的图片。

我努力把我引用的代码都标记出处,如果我遗漏了谁请原谅我。

我不能忘记感谢 Mikail Freitas 帮助我识别泛型协议初始化时的错误。我们永远不明白为什么在一个案例中运行好好地,而另一个则不起作用。

这篇关于[译] 使用流动控制器(Flow Controller )实现 MVVM 协议模型的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1023330

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

中文分词jieba库的使用与实景应用(一)

知识星球:https://articles.zsxq.com/id_fxvgc803qmr2.html 目录 一.定义: 精确模式(默认模式): 全模式: 搜索引擎模式: paddle 模式(基于深度学习的分词模式): 二 自定义词典 三.文本解析   调整词出现的频率 四. 关键词提取 A. 基于TF-IDF算法的关键词提取 B. 基于TextRank算法的关键词提取

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

Hadoop数据压缩使用介绍

一、压缩原则 (1)运算密集型的Job,少用压缩 (2)IO密集型的Job,多用压缩 二、压缩算法比较 三、压缩位置选择 四、压缩参数配置 1)为了支持多种压缩/解压缩算法,Hadoop引入了编码/解码器 2)要在Hadoop中启用压缩,可以配置如下参数

Makefile简明使用教程

文章目录 规则makefile文件的基本语法:加在命令前的特殊符号:.PHONY伪目标: Makefilev1 直观写法v2 加上中间过程v3 伪目标v4 变量 make 选项-f-n-C Make 是一种流行的构建工具,常用于将源代码转换成可执行文件或者其他形式的输出文件(如库文件、文档等)。Make 可以自动化地执行编译、链接等一系列操作。 规则 makefile文件

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

使用opencv优化图片(画面变清晰)

文章目录 需求影响照片清晰度的因素 实现降噪测试代码 锐化空间锐化Unsharp Masking频率域锐化对比测试 对比度增强常用算法对比测试 需求 对图像进行优化,使其看起来更清晰,同时保持尺寸不变,通常涉及到图像处理技术如锐化、降噪、对比度增强等 影响照片清晰度的因素 影响照片清晰度的因素有很多,主要可以从以下几个方面来分析 1. 拍摄设备 相机传感器:相机传

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题

题库来源:安全生产模拟考试一点通公众号小程序 2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题是由安全生产模拟考试一点通提供,流动式起重机司机证模拟考试题库是根据流动式起重机司机最新版教材,流动式起重机司机大纲整理而成(含2024年流动式起重机司机证模拟考试题库及流动式起重机司机理论考试试题参考答案和部分工种参考解析),掌握本资料和学校方法,考试容易。流动式起重机司机考试技

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象