Swift中的存储库设计模式

一种查询模型的干净方法

它解决什么问题?

如果您需要一遍又一遍地从代码中的不同位置查询模型对象,那么存储库对于提供一个用于处理模型并删除重复查询代码的单入口点非常有帮助。您可以更进一步地将其与协议一起使用,这样您可以轻松地切换实现(例如用于单元测试),也可以将其与泛型一起使用以进行更多的“鼓” *泛型抽象。在本文中,我将介绍所有这些情况。

草绘场景。

假设您有一些代码可以从API提取数据并将其映射到模型对象。在此示例中,我将从服务器中获取文章列表。

这可能看起来有点时髦,但是它只是RxSwift,使用Moya作为网络抽象层,但是了解发生的事情并不重要。数据检索的方式完全取决于您。

这段代码确实

  1. 到服务器的GET请求
  2. 将返回的JSON映射到Article对象的数组
  3. 完成所有工作后,将调用该闭包。

为什么需要存储库?

好吧,目前我们还没有。如果您在整个代码库中仅调用一次API,则添加存储库可能会显得过大(或者有些人说过度设计)。

好的...但是什么时候方便使用存储库对象?
假设您的代码库开始增长,您需要编写代码来一遍又一遍地获取文章。您可能会说“让我们复制代码并将其粘贴到需要提取所有文章的位置”。

没有伤害,没有人死亡。对?

那时,一个大的红色警报应该开始在您的大脑中闪烁。

你好仓库。

存储库只是一个对象,该对象封装了所有代码以在一个地方查询模型,因此如果需要,您可以单点进入获取所有文章。

让我们创建一个存储库对象,该对象提供一个公共API来获取文章。

现在,我们可以调用此方法,而不必担心幕后发生的事情来获取实际的文章。
只需调用该方法,即可获得文章。好吧
但是,等等,还有更多!

处理所有文章互动

我们可以使用存储库添加更多与模型对象进行交互的方法。大多数情况下,您想在模型上执行CRUD(创建,读取,更新,删除)操作。好吧,只需在存储库中添加这些操作的逻辑即可。

这使您可以在整个代码中使用一个不错的API,而不必一遍又一遍地重复相同的代码。

在实践中,使用存储库看起来像这样。

很好而且可读,对吗?但是,等待它变得更好。

上电:协议

在前面的代码中,我始终使用“从API获取数据”的示例。但是,如果您需要添加支持以从本地JSON文件而不是在线源加载数据该怎么办。

好吧,如果您创建了一个列出方法名称的协议,则可以为在线API创建一个实现,并让数据脱机。

看起来可能像这样。

协议只是说“如果您符合我的要求,则需要具有这些方法的签名,但我不在乎实际的实现!”

太好了,您可以创建一个WebArticleRepository和LocalArticleRepository。它们都具有协议中列出的所有方法,但是您可以编写2种完全不同的实现。

上电:单元测试

当您要对代码进行单元测试时,使用协议也非常方便,因为您可以创建另一个实现存储库协议的对象,而只返回模拟数据。

如果将其与依赖项注入一起使用,则可以非常轻松地测试特定对象。

一个例子

假设您有一个视图模型,并且该视图模型通过存储库获取其数据。

如果您想测试视图模型,则可以从网上获取文章。
这实际上不是我们想要的。我们希望我们的测试尽可能地具有确定性。在这种情况下,从网络上检索到的文章可能会随时间变化,测试运行时可能没有互联网连接,服务器可能会关闭,…这些都是我们测试失败的所有可能情况,因为它们是无法控制当我们进行测试时,我们希望/需要得到控制。

幸运的是,解决这个问题实际上非常简单。

您好,依赖注入。

您只需要通过初始化程序设置articleRepo属性。默认情况下,将是生产代码所需的情况,编写单元测试时,可以用模拟版本换出存储库。

但是也许您在想,类型呢? WebArticleRepository不是MockArticleRepository,因此编译器不会抱怨吗?好吧,如果您将协议用作类型,则不行。这样,我们就让编译器知道,只要它符合ArticleRepository协议(Web和MockArticleRepository都可以这样做),就允许所有内容。

最终代码如下所示。

在单元测试中,您可以像这样交换它。

现在,您可以完全控制存储库返回的数据。

超级加电:通用

通过使用泛型,您甚至可以更进一步。如果您考虑一下,大多数存储库始终具有相同的操作

  1. 得到所有的东西
  2. 得到一些东西
  3. 插入一些东西
  4. 删除东西
  5. 更新一件东西

好吧,唯一不同的是“事物”一词,因此这可能是将协议与泛型一起使用的理想选择。听起来可能很复杂,但实际上操作起来很简单。

首先,我们将协议重命名为Repository,以使其更加通用。
然后,我们将删除所有“文章”类型,并用魔术T代替它们。但是字母T只是……我们想要的任何东西的替代。我们只需要将T标记为协议的关联类型。

因此,现在我们可以将此协议用于我们拥有的任何模型对象。

1.文章存储库

编译器会将T的类型推断为Article,因为通过实现这些方法,我们指定了T是什么。在这种情况下,一个Article对象。

2.用户存储库

而已。

希望您喜欢这篇文章,如果您有任何疑问或评论,请在下面提问,或在Twitter上与我联系并进行聊天。