让我们尝试将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 objects
和data mapping layers
:
存储库在域和数据映射层之间进行调解
和
在幕后,存储库将元数据映射 (329) 与查询对象 (316) 结合在一起,元数据映射在元数据中保存对象关系映射的详细信息。
元数据映射基本上意味着即如何将 SQL 列映射到 java 类字段。
如前所述,ContentProviderCursor
从 query() 操作返回一个对象。从我的角度来看,游标不是域对象。此外,从游标到域对象的映射必须由客户端(使用 ContentProvider)完成。因此,从我的角度来看,ContentProvider 中完全缺少数据映射。此外,客户端可能必须使用 a ContentResolver
too 来获取域对象(数据)。在我看来,这个 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