查看原文
其他

iOS 中的六边形架构

Oleksandr Stepan 小集 2022-08-27

作者 | Oleksandr Stepanov 
来源 | Medium Better Programming,点击“阅读原文”查看作者更多文章

这是一个系列文章,主要介绍在 iOS 中采用的“六边体系结构”模式。这是第一部分,主要介绍理论部分,会比较晦涩。下一部分将涵盖实践以及示例项目。

在谈论 iOS 应用的架构时,我们通常会想到与 UI 相关的 MVC,MVVM,VIPER 等。但是,应用程序的架构不应该局限于 UI。前面这些模式都有一个称为模型或实体的部分,用于负责领域或业务逻辑、第三方依赖集成、与系统和框架交互等。这时候六边形架构就可以派上用场了,它能让事情变得更清晰。

最初,Alistair Cockburn 提出这个架构时,是基于主要主件将其命名为 “端口与适配器架构(Ports & Adapters Architecture)”。六边形架构一词的流行源于组件之间连接方式的示意图。

六边形架构是这样一种模式,将开发人员的关注点从概念分层转移到 app 内部和外部的不同之处。内部是领域或业务逻辑,外部则是 UI、网络、传感器、数据存储等。内外部分的连接依赖于端口(ports)及对应的称为适配器(adapters)的实施接口。

定义

领域模型

领域模型是一个概念模型,表示需要在软件中实现的有意义的逻辑。从本质上讲,它是应用程序的业务逻辑与数据模型结合在一起。

端口

端口是与领域模型交互的消费者不可知的出入口。它是访问业务逻辑的媒介。在Swift中,端口是与应用程序中的用例相对应的协议。端口被适配器使用。

适配器

适配器是领域模型和应用程序所需服务之间的桥梁。其目的是使各种外部参与者和业务逻辑之间以相互独立的方式进行通信。

在六边形体系结构中,所有外部角色都通过适配器与端口交互。一个适配器可以是 BLE 服务,负责与 Core Bluetooth iOS 框架进行交互。另一个可能是与远程存储交互的类似控制器的服务。

端口和适配器的类型

• 主适配器 是在领域模型上启动某些操作的适配器。在 MVP 中,presenter 就是一个与是领域模型(即应用程序的业务逻辑)进行交互的适配器。在 MVVM 中,是一个 view model;在 VIPER 或 RIB 中,是一个交互器。在这种情况下,端口扮演的角色是核心中领域模型类的协议。换句话说,这些适配器是通过协议与领域模型交互。

• 辅助适配器 表示与后端工具(如数据库,网络API,传感器等)的连接。它们直接与原生或特定 API 交互,并且由领域模型通过适当的端口调用。辅助端口的一个示例是用于存储模型对象的接口。该接口仅声明用于从存储中检索,更新和删除对象的方法。它没有告诉您这些对象的具体存储方式。

如您所见,六边形体系结构通过将逻辑封装在应用程序的不同层中来更好地分离关注点。这样可以实现更高级别的隔离、可测试性以及对特定领域的代码的控制。应用程序的每一层都有严格的职责和要求。这样就确定了某些功能的位置以及这些层之间如何交互的明确界限。

实际上,领域模型不仅可以是一个类,还可以是一组类,每个类负责自己特定的功能。这些类通常是单例,可以直接相互连接,也可以通过协调器相互连接,协调器的目的是实例化和保存引用,并进行编排。

优点

• 外部服务独立性。您可以先开发应用程序的核心部分,然后再考虑要使用哪种类型的数据存储。通过定义存储的端口和适配器,您可以自由使用任何技术实现。

• 关注点分离。外层通常比业务逻辑更改得更频繁。例如,UI 或第三方 API 的发展通常比应用程序的业务规则更新得更快。这种分离使您可以快速迭代外层,而无需改动内层。

• 能够用单元测试覆盖业务逻辑。现在,当封装业务逻辑的领域模型与外部依赖项无关时,通过模拟其适配器进行测试就容易得多。

• 适配器是可更换的。适配器的目的是抽象外部服务和依赖项的实际实现。这种抽象允许应用程序接收请求并发送对各种外部需求的响应,而不必知道实际的实现。这样就可以用符合同一接口的其他实现替换适配器。

• 良好的可维护性。可维护性是指没有技术债务,即,应用程序一个地方的更改不会直接影响其他方面。添加功能不需要大规模地更改代码。上面提到的优点共同提供了高度的可维护性。

缺点

在我看来,唯一的缺点是需要定义大量的实体,例如类和协议,以在项目中实现此架构。显然,在无架构方案(no-architecture approach)中,视图控制器可以直接与 Realm,Firebase 或 Core Bluetooth 进行交互。但是,这种设计没有上述任何好处。

小结

持怀疑态度的读者可能会说presents,view model或交互器实际上是应用程序处理业务逻辑的地方。这是部分正确的,但通常情况下,优秀的工程师不会在此处与第三方或原生框架进行直接交互。为了重用其功能(例如 API 服务,Firebase 服务,蓝牙服务等),工程师会选择其置于另一种服务级别上。然后这些服务代替领域模型,而它与视图之间的中间层将成为适配器。为了用单元测试覆盖这一层,需要模拟这些服务,然后通过协议(即端口)与它们连接。

这些服务可以直接与外层交互,但是如果这些连接未抽象并且无法模拟,它们也将变得不可测试。最后,在此侧添加端口和适配器会将我们引向本文的的主题。

最后,我想引用 Robert C. Martin 的著作《Clean Architecture》中对架构的定义:

“软件系统的体系结构是构建它的人赋予该系统的形状。这种形状的形式在于将该系统划分为各个组件、这些组件的布置以及这些组件彼此之间进行通信的方式。”



推荐阅读
• Swift 6 在路上
• 规范化团队 git 提交信息
• iOS 内存泄漏场景与解决方案
• FB:再见,React Native
• 几个 JavaScript 性能优化小 Tip
• 划重点:《App Store 审核指南》更新条目一览


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存