40

存储库模式Hieatt 和 Rob Mee定义为使用类似集合的接口访问域对象在域和数据映射层之间进行调解的设计模式。

MSDN 存储库模式

基本上,它将一个或多个 I/O 设备(云、磁盘、数据库等)抽象为一个通用的类集合接口,您可以在其中读取、写入、查找和删除数据

Fernando Cejas 的 Android Clean Architecture中,应用程序所需的所有数据都来自该层,通过存储库实现(接口位于域层中),该存储库实现使用存储库模式,其策略通过工厂选择不同的数据源,具体取决于一定条件下。

内容提供商

然而,正如Douglas Schmidt教授在Coursera 课程中所指出的那样,内容提供者管理和调解对一个或多个应用程序的中央数据存储库的访问。

内容提供商

Programming Android一书中,内容提供者被用作RESTful Web 服务的外观。这种方法最初是由 Virgil Dobjanschi 在 Google I/O 2010 期间提出的。

因此,与其使用内容提供者来访问本地 SQLite 数据库,为什么不将其用作存储库模式本身呢?

在此处输入图像描述

4

8 回答 8

27

让我们尝试将Martin Fowler(与 Dave Rice、Matthew Foemmel、Edward Hieatt、Robert Mee 和 Randy Stafford)一书的“企业应用程序架构模式”中的存储库模式定义与我们所知道的进行比较ContentProviders

书中指出:

存储库使用类集合接口在域和数据映射层之间进行调解,以访问域对象。

重要的是accessing domain objects。因此,乍一看,存储库模式似乎仅用于访问(查询)数据。但是,使用 a ContentProvider,您不仅可以访问(读取)数据,还可以插入、更新或删除数据。然而,书上说:

对象可以添加到存储库中,也可以从存储库中删除,就像它们可以从简单的对象集合中一样,存储库封装的映射代码将在幕后执行适当的操作。

所以,是的,Repository 和 ContentProvider 似乎提供了相同的操作(非常高级的观点),尽管这本书明确指出simple collection of objects这是不正确的,ContentProvider因为它需要特定于 androidContentValues并且Cursor来自客户端(使用特定的ContentProvider)与之交互。

此外,这本书提到domain objectsdata mapping layers

存储库在域和数据映射层之间进行调解

在幕后,存储库将元数据映射 (329) 与查询对象 (316) 结合在一起,元数据映射在元数据中保存对象关系映射的详细信息。

元数据映射基本上意味着即如何将 SQL 列映射到 java 类字段。

如前所述,ContentProviderCursor从 query() 操作返回一个对象。从我的角度来看,游标不是域对象。此外,从游标到域对象的映射必须由客户端(使用 ContentProvider)完成。因此,从我的角度来看,ContentProvider 中完全缺少数据映射。此外,客户端可能必须使用 a ContentResolvertoo 来获取域对象(数据)。在我看来,这个 API 与书中的定义明显矛盾:

存储库还支持在域和数据映射层之间实现清晰分离和单向依赖的目标

接下来我们重点讲一下Repository模式的核心思想:

在具有许多域对象类型和许多可能查询的大型系统中,存储库减少了处理所有查询所需的代码量。存储库促进了规范模式(以此处示例中的条件对象的形式),它封装了要以纯面向对象方式执行的查询。因此,可以删除所有在特定情况下设置查询对象的代码。客户不需要用 SQL 思考,可以纯粹根据对象编写代码。

ContentProvider 需要一个 URI(字符串)。所以它不是真正的“面向对象的方式”。ContentProvider 也可能需要 aprojection和 a where-clause

所以有人可能会争辩说 URI 字符串是某种封装,因为客户端可以使用这个字符串而不是编写特定的 SQL 代码,例如:

使用存储库,客户端代码构建标准,然后将它们传递给存储库,要求它选择匹配的对象。从客户端代码的角度来看,没有查询“执行”的概念;而是通过查询规范的“满意度”来选择适当的对象。

使用 URI(字符串)的 ContentProvider 似乎与该定义并不矛盾,但仍然错过了强调的面向对象的方式。此外,字符串不是可重用的标准对象,可以以一般方式重用以构成标准规范,以“减少处理所有查询所需的代码量”。

例如,要按名称查找人员对象,我们首先创建一个条件对象,设置每个单独的条件,如下所示:criteria.equals(Person.LAST_NAME, "Fowler") 和 criteria.like(Person.FIRST_NAME, "M")。然后我们调用 repository.matching(criteria) 来返回一个域对象列表,这些对象代表姓 Fowler 和名字以 M 开头的人。

正如您已经说过(在您的问题中)存储库对于隐藏不同的数据源作为客户不知道的实现细节也很有用。这适用于 ContentProviders 并在书中指定:

Repository 的对象源可能根本不是关系数据库,这很好,因为 Repository 很容易通过专门的策略对象替换数据映射组件。出于这个原因,它在具有多个数据库模式或域对象源的系统中特别有用,以及在测试期间需要专门使用内存中对象以提高速度时。

因为 Repository 的接口屏蔽了领域层对数据源的感知,我们可以在不改变任何客户端调用的情况下重构 Repository 内部查询代码的实现。实际上,域代码不需要关心域对象的来源或目的地。


因此得出结论:Martin Fowler 等人的一些定义。book 匹配 ContentProvider 的 API(如果你忽略了这本书强调面向对象的事实):

  • 隐藏存储库 / ContentProvider 具有不同数据源的事实
  • 客户端永远不必在特定于数据源的 DSL(如 SQL)中编写查询。如果我们认为 URI 不是特定于数据源的,那么 ContentProvider 也是如此。
  • Repository 和 ContentProvider 都具有相同的“高级”操作集:读取、插入、更新和删除数据(如果您忽略了 Fowler 谈论了很多关于面向对象和对象集合而 ContentProvider 使用 Cursor 和 ContentValues 的事实)

但是,ContentProvider 确实遗漏了本书中描述的存储库模式的一些关键点:

  • 由于 ContentProvider 使用 URI(也是 where 子句的字符串),因此客户端不能重用 Matching Criteria 对象。这是需要注意的重要一点。这本书清楚地指出存储库模式很有用“在具有许多域对象类型和许多可能查询的大型系统中,存储库减少了处理所有查询所需的代码量”。不幸的是,ContentProvider 没有criteria.equals(Person.LAST_NAME, "Fowler")可以重用并用于组成匹配条件的 Criteria 对象(因为您必须使用字符串)。
  • ContentProvider 在返回一个Cursor. 这很糟糕,因为客户端(使用 ContentProvider 访问数据)必须将 Cursor 映射到域对象。此外,这意味着客户了解存储库内部信息,例如列名。“存储库是一种很好的机制,可以提高广泛使用查询的代码的可读性和清晰度。” 对于 ContentProviders 来说,这当然不是真的。

所以不,ContentProvider 不是“企业应用程序架构模式”一书中定义的存储库模式的实现,因为它至少遗漏了我上面指出的两个基本内容。

另外,请注意,正如书名所暗示的,存储库模式旨在用于执行大量查询的企业应用程序。

Android 开发人员倾向于使用术语“存储库模式”,但实际上并不意味着 Fowler 等人描述的“原始”模式。(查询标准的高可重用性)而是意味着隐藏底层数据源(SQL、云等)和域对象映射的接口。

更多信息:http: //hannesdorfmann.com/android/evolution-of-the-repository-pattern

于 2017-04-26T23:47:14.990 回答
7

简短的回答:Contentprovider 是数据源而不是存储库。

SQL-Database/Android-Contentproviders/Repositories 的目的是创建/读取/更新/删除/查找数据

存储库通常在特定于高级业务的 java 类(如 Customer、Order、Product 等)上运行,而 SQL-Database 和 Android-Contentproviders 在低级表、行和列上作为数据源运行。

因为 SQL-Database 不是存储库,所以Android-Contentprovider 也不是存储库

但是您可以使用底层的 Contentprovider 来实现存储库

于 2017-04-20T12:58:18.570 回答
6

我会提到 Dianne Hackborn(来自 Android 框架团队)来发表我的看法。

内容提供商

最后,ContentProvider 是一个相当专业的工具,用于将数据从应用程序发布到其他地方。人们通常将它们视为数据库的抽象,因为对于这种常见情况,它们内置了很多 API 和支持......但从系统设计的角度来看,这不是他们的重点。

这些对系统来说是一个进入应用程序的入口点,用于发布由 URI 方案标识的命名数据项。因此,应用程序可以决定如何将其包含的数据映射到 URI 命名空间,将这些 URI 分发给其他实体,这些实体又可以使用它们来访问数据。这允许系统在管理应用程序时执行一些特定的操作:

• 分发 URI 不需要应用程序保持运行,因此它们可以在拥有应用程序死机的情况下到处乱跑。只有当有人告诉系统“嘿,给我这个 URI 的数据”时,它才需要确保拥有该数据的应用程序正在运行,以便它可以要求应用程序检索并返回数据。

• 这些URI 还提供了一个重要的细粒度安全模型。例如,应用程序可以将其拥有的图像的 URI 放在剪贴板上,但将其内容提供者锁定,这样任何人都无法自由访问它。当另一个应用程序从剪贴板中拉出该 URI 时,系统可以给它一个临时的“URI 权限授予”,以便它只能访问该 URI 后面的数据,而不能访问应用程序中的其他数据。

我们不关心的:

如何实现内容提供者背后的数据管理并不重要。如果您不需要 SQLite 数据库中的结构化数据,请不要使用 SQLite。例如,FileProvider 帮助程序类是一种通过内容提供程序使您的应用程序中的原始文件可用的简单方法。

此外,如果您不从您的应用程序中发布数据以供其他人使用,则根本不需要使用内容提供程序。确实,由于围绕内容提供程序构建的各种帮助程序,这可以是一种将数据放入 SQLite 数据库并使用它来填充 UI 元素(如 ListView)的简单方法。但是,如果任何这些东西让您尝试做的事情变得更加困难,那么请不要使用它,而是为您的应用使用更合适的数据模型。</p>

全文在这里: https: //plus.google.com/+DianneHackborn/posts/FXCCYxepsDU

于 2017-04-20T14:32:04.087 回答
5

感谢这个问题,这是一个很好的观察:)。恕我直言,这不是一个是或否的问题,因为它很笼统,就像大多数设计模式相关的主题一样。答案取决于您考虑的背景:

如果您有一个完全依赖于平台的应用程序,这意味着考虑 Android 生态系统的上下文,那么是的,ContentProvider 是 Repository 模式的实现。这里的论点是内容提供者旨在解决存储库模式旨在解决的一些相同挑战:

  • 它提供了对数据层的抽象,因此代码不一定依赖于存储环境
  • 没有来自任何地方的直接数据访问。您可以将所有 SQL 查询(或其他)放在一个地方。当我第一次将 ContentProvider 实现为一个菜鸟时,这对我来说就像是一个启示,我的代码看起来多么干净,我可以多么舒服地进行更改
  • 集中数据并在多个客户端(其他应用程序,您已经知道的搜索小部件)之间共享数据,并提供数据安全机制
  • 您绝对可以定义与数据相关的行为(一种方法是使用 ContentObserver)
  • 这是一种很好的方法,可以迫使您从早期阶段就考虑到单元测试/自动化测试来组织代码

如果您将以上所有内容与存储库模式的原则放在一起,就会发现有一些严重的相似之处。并非所有人都满意,但核心思想是相同的。

现在,考虑到在多种环境(即网络、移动设备、PC)中大规模运行的应用程序,需求完全改变了。这是一个坏主意,因为每个人都建议依赖 ContentProvider 作为设计模式。它本身不一定是一个坏主意,但必须实现设计模式,以便其他人可以尽快理解您的代码。你看,即使在这里,每个人都建议 ContentProvider 的一个常见用途:作为数据源,或者无论如何依赖于平台的东西。因此,如果您在具有已知目的的组件之上强制实施,事情可能会变得相当不清楚。以经典模式组织代码要好得多。

tl;博士; 如果您的应用在您的 Android 设备上是孤立的,那么您绝对可以合并这两个概念。如果您的应用程序在多个平台上大规模使用,它会更简洁,以经典方式组织您的代码。

于 2017-04-25T14:07:17.617 回答
2

恕我直言,最好将 Contentprovider 视为数据源,尽管数据可以以多种方式存储(SQLite 数据库、文件等),以保持架构和 Android 框架之间的一些独立性。

Google 存储库提供了一些架构示例。其中一个包含一个带有内容提供者和存储库的架构示例:

googlesamples/android-architecture/todo-mvp-contentproviders

节选:

然后,您可以使用内容提供程序来支持此示例未涵盖的其他功能,从而提供以下可能的好处:

  • 允许您与其他应用程序安全地共享存储在您的应用程序中的数据。
  • 在您的应用中添加对自定义搜索的支持。
  • 开发访问应用程序中数据的小部件。

架构 todo-mvp-contentproviders

于 2017-04-25T15:38:09.933 回答
2

这是一个有趣的问题。我想我的第一个答案是否定的,Content Provider 不是存储库模式的实现。

正如您所提到的,存储库模式旨在将业务逻辑(域)与数据层分开。这种方法允许您为业务逻辑创建单元测试(因此域根本不应该依赖于 Android)。通过使用内容提供者,您将需要在您的域中拥有某种 Android 对象。

您可以想象一种将 Content Provider 逻辑隐藏在 Interface 后面的方法,但是您将失去 Content Provider 允许您做的许多好事情。

如果你对 Android 架构感兴趣,我建议你看看这个 Github 项目Android Clean Architecture。您会找到一种很好的方法来分离您的表示层、域和数据层,并且域和数据之间的通信是通过使用存储库模式完成的。

希望这会有所帮助!

于 2017-04-06T15:18:32.660 回答
1

使用 ContentProviders 作为 Repository 的问题在于,您将模型中的依赖项添加到 Android 框架。使用存储库模式可以让您轻松地模拟、测试和替换实现。

正确的做法是将 ContentProvider 隐藏在接口下,并让模型通过该接口访问数据。这样,您的代码就与平台分离。

基本上,ContentProvider 是您要抽象的 I/O 源。

于 2017-04-20T11:33:49.820 回答
0

Content Provider 是一个Android组件,如果您将存储库概念与此组件混合使用,味道会不好,它会在您的应用程序中创建阻塞依赖。

于 2017-04-20T11:27:02.067 回答