Content Provider 是 Repository 模式的实现吗?

Is Content Provider an implementation of Repository Pattern?

Repository Pattern is defined by Hieatt and Rob Mee 作为设计模式 使用 collection-like 接口访问域对象 .

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

基本上它将一个或多个 I/O 设备(云、磁盘、数据库等)抽象为一个通用的 collection-like 接口,您可以在其中 read, write, seek and delete data.

Fernando Cejas's Android Clean Architecture,应用程序所需的所有数据都来自这一层,通过存储库实现(接口在域层中)使用存储库模式和策略,通过工厂选择不同的数据源取决于某些条件。

然而,正如教授所指出的Douglas Schmidt at Coursera course, content provider manages and mediates access to a central repository of data to one or more applications

书中Programming Android, content providers are used as a Facade for a RESTful Web Service。这种方法最初由 提出 Virgil Dobjanschi Google I/O 2010.

因此,为什么不使用内容提供程序来 access the local SQLite database,为什么不将其用作存储库模式本身呢?

这是个有趣的问题。我想我的第一个答案是否定的,Content Provider 不是 Repository Pattern 的实现。

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

您可以想象一种将内容提供程序逻辑隐藏在接口后面的方法,但是您将失去内容提供程序允许您做的许多好事。

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

希望对您有所帮助!

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

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

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

基本上,ContentProvider 是您要提取的 I/O 来源。

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

SQL-Database/Android-Contentproviders/Repositories的目的是create/read/update/delete/find数据

存储库通常针对特定的高级业务运行 java 类(例如客户、订单、产品......) 而 SQL-数据库和 Android-Contentproviders 在低级别 table 上运行,行和列作为 数据源

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

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

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

ContentProvider

Finally, the ContentProvider is a fairly specialized facility for publishing data from an app to other places. People generally think of them as an abstraction on a database, because there is a lot of API and support built in to them for that common case... but from the system design perspective, that isn't their point.

What these are to the system is an entry-point into an app for publishing named data items, identified by a URI scheme. Thus an app can decide how it wants to map the data it contains to a URI namespace, handing out those URIs to other entities which can in turn use them to access the data. There are a few particular things this allows the system to do in managing an app:

• Handing out a URI doesn't require the app remain running, so these can go all over the place with the owning app being dead. Only at the point where someone tells the system, "hey give me the data for this URI" does it need to make sure the app owning that data is running, so it can ask the app to retrieve and return the data.

• These URIs also provide an important fine-grained security model. For example, an application can place the URI for an image it has on the clipboard, but leave its content provider locked up so nobody can freely access it. When another app pulls that URI off the clipboard, the system can give it a temporary "URI permission grant" so that it is allowed to access the data only behind that URI, but nothing else in the app.

What we don't care about:

It doesn't really matter how you implement the data management behind a content provider; if you don't need structured data in a SQLite database, don't use SQLite. For example, the FileProvider helper class is an easy way to make raw files in your app available through a content provider.

Also, if you are not publishing data from your app for others to use, there is no need to use a content provider at all. It is true, because of the various helpers built around content providers, this can be an easy way to put data in a SQLite database and use it to populate UI elements like a ListView. But if any of this stuff makes what you are trying to do more difficult, then feel free to not use it and instead use a more appropriate data model for your app.

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

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

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

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

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

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

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

恕我直言,最好将 Contentprovider 视为数据源,尽管数据可以以多种方式存储(SQLite 数据库、文件等),以在体系结构和 Android 框架。

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

googlesamples/android-architecture/todo-mvp-contentproviders

节选:

You could then use content providers to support additional features that are not covered by this sample, providing the following possible benefits:

  • Allow you to securely share data stored in your app with other apps.
  • Add support for custom searches in your app.
  • Develop widgets which access data in your app.

让我们尝试比较 Martin Fowler 所著 "Patterns of Enterprise Application Architecture" 一书中的存储库模式定义 (与 Dave Rice、Matthew Foemmel、Edward Hieatt、Robert Mee 和 Randy Stafford 一起) 以及我们所了解的 ContentProviders

书上说:

A Repository Mediates between the domain and data mapping layers using a collection-like interface for accessing domain objects.

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

Objects can be added to and removed from the Repository, as they can from a simple collection of objects, and the mapping code encapsulated by the Repository will carry out the appropriate operations behind the scenes.

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

此外,书中提到了 domain objectsdata mapping layers

A Repository Mediates between the domain and data mapping layers

Under the covers, Repository combines Metadata Mapping (329) with a Query Object (316) Metadata Mapping holds details of object-relational mapping in metadata.

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

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

Repository also supports the objective of achieving a clean separation and one-way dependency between the domain and data mapping layers

接下来重点说说Repository模式的核心思想:

In a large system with many domain object types and many possible queries, Repository reduces the amount of code needed to deal with all the querying that goes on. Repository promotes the Specification pattern (in the form of the criteria object in the examples here), which encapsulates the query to be performed in a pure object-oriented way. Therefore, all the code for setting up a query object in specific cases can be removed. Clients need never think in SQL and can write code purely in terms of objects.

ContentProvider 需要 URI(字符串)。所以它不是真正的 "object-oriented way"。此外,ContentProvider 可能需要 projectionwhere-clause.

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

With a Repository, client code constructs the criteria and then passes them to the Repository, asking it to select those of its objects that match. From the client code's perspective, there's no notion of query "execution"; rather there's the selection of appropriate objects through the "satisfaction" of the query's specification.

使用 URI(字符串)的 ContentProvider 似乎与该定义并不矛盾,但仍然没有强调面向对象的方式。字符串也不是可重复使用的标准对象,可以以一般方式重复使用以将标准规范组合为 "reduces the amount of code needed to deal with all the querying that goes on."

For example, to find person objects by name we first create a criteria object, setting each individual criterion like so: criteria.equals(Person.LAST_NAME, "Fowler"), and criteria.like(Person.FIRST_NAME, "M"). Then we invoke repository.matching(criteria) to return a list of domain objects representing people with the last name Fowler and a first name starting with M.

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

The object source for the Repository may not be a relational database at all, which is fine as Repository lends itself quite readily to the replacement of the data-mapping component via specialized strategy objects. For this reason it can be especially useful in systems with multiple database schemas or sources for domain objects, as well as during testing when use of exclusively in-memory objects is desirable for speed.

Because Repository's interface shields the domain layer from awareness of the data source, we can refactor the implementation of the querying code inside the Repository without changing any calls from clients. Indeed, the domain code needn't care about the source or destination of domain objects.


总结一下:Martin Fowler 等人的一些定义。本书匹配一个ContentProvider的API(如果忽略本书强调面向对象的事实):

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

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

  • 由于 ContentProvider 使用 URI(也是 where 子句的字符串),客户端无法重复使用 Matching Criteria 对象。这是需要注意的重要事项。书上说的很清楚,repository pattern 很有用"In a large system with many domain object types and many possible queries, Repository reduces the amount of code needed to deal with all the querying that goes on"。不幸的是,ContentProvider 没有像 criteria.equals(Person.LAST_NAME, "Fowler") 这样的 Criteria 对象可以重复使用并用于组成匹配条件(因为你必须使用字符串)。
  • ContentProvider 完全错过了数据映射,因为它 returns 和 Cursor。这非常糟糕,因为客户端(使用 ContentProvider 访问数据)必须将 Cursor 映射到域对象。此外,这意味着客户端了解存储库内部信息,例如列名。 "Repository can be a good mechanism for improving readability and clarity in code that uses querying extensively." ContentProvider 肯定不是这样。

所以不,ContentProvider 不是本书 "Patterns of Enterprise Application Architecture" 中定义的存储库模式 的实现,因为它至少遗漏了我已经指出的两个基本内容以上。

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

Android 开发人员倾向于使用术语 "Repository pattern" 但实际上并不意味着 Fowler 等人描述的 "original" 模式。 (查询条件的高可重用性)而是意味着隐藏底层数据源(SQL、云等)和域对象映射的接口。

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