swift 听筒模式

(Top highlight)

(Background)

All apps developed require data of some description. This data is stored somewhere, could be on the device itself, in a remote database/service or a combination. Let’s take a look at the most common sources of data:

开发的所有应用程序都需要一些描述数据。 这些数据存储在某个地方,可以在设备本身上,也可以在远程数据库/服务中或它们的组合中。 让我们看一下最常见的数据源:

Each of these methods saves data in a different format. Now I’m sure you will have used at least one of these methods in your apps at some point to retrieve / save data.

这些方法中的每一种都以不同的格式保存数据。 现在,我确定您有时会在您的应用程序中至少使用这些方法之一来检索/保存数据。

When not using the repository pattern it is quite common to access and use these elements directly, either in your ViewController or in some other part of your app depending how it is structured.

不使用存储库模式时,通常会在ViewController或应用程序的其他部分直接访问和使用这些元素,具体取决于存储库的结构。

(The problem)

What’s the problem with this approach? Your app becomes difficult to maintain. Now if you only have a small app with a few screens then this isn’t much of a problem as there are only a few elements to change.

这种方法有什么问题? 您的应用程序变得难以维护。 现在,如果您只有一个带有几个屏幕的小型应用程序,那么这并不是什么大问题,因为只有几个元素需要更改。

However, what if you are working on a large app with several developers and lots of code? You could have NSManagedObjects or Codable objects littered throughout the codebase for example. What happens if you wish to remove Core Data? Perhaps move to realm? You would need to modify all of the classes in your codebase where you had used your Core Data objects.

但是,如果您正在开发具有多个开发人员和大量代码的大型应用程序,该怎么办? 例如,您可能在整个代码库中堆满了NSManagedObjects或Codable对象。 如果您希望删除核心数据会怎样? 也许走向领域? 您将需要修改您的代码库中使用过Core Data对象的所有类。

Similarly, if you are using Codable objects directly from your JSON response. What happens when your backend team changes the API or you switch to a different API provider? The structure of the data may change which means your Codable objects might change. Again you will need to modify a large number of classes if you are working on a large app.

同样,如果您直接从JSON响应中使用Codable对象。 当您的后端团队更改API或切换到其他API提供程序时会发生什么? 数据的结构可能会更改,这意味着您的Codable对象可能会更改。 同样,如果您正在处理大型应用程序,则需要修改大量的类。

We can also apply this to the other options such as accessing data from 3rd party frameworks. If we use the objects returned from the framework directly, they will all need changing if we change provider or the SDK changes.

我们还可以将其应用于其他选项,例如从第3方框架访问数据。 如果我们直接使用从框架返回的对象,则在更改提供者或更改SDK时,都将需要更改它们。

There is also the question of query language. Web services use headers and URLQueryItem, Core Data uses Predicates and so on. Every entry point to query the data must know and understand the underlying query language in order to get the information it once. Again, if this changes we need change every query point to the new format.

还有查询语言的问题。 Web服务使用标头和URLQueryItem ,核心数据使用谓词等。 每个查询数据的入口点都必须了解并理解基础查询语言,以便一次获取信息。 同样,如果发生这种变化,我们需要将每个查询点都更改为新格式。

Let’s have a look at the diagram below:

让我们看一下下图:

Here we have an app structure that is making use of Core Data. There is an object that is being used to access the stack that returns some data. Let’s say for this example that it is news articles. These new articles must inherit from NSManagedObject to be used in Core Data. Now if our data layer is returning NSManagedObjects to the rest of our app structure we now have a dependency between Core Data and the rest of the files in our app. If we wish to move to Realm for example, or switch to using some other form of data store we would need to modify all the of files in the app. The app in this example is only small, imagine having to do that for a much bigger app!

在这里,我们有一个利用核心数据的应用程序结构。 有一个对象用于访问返回一些数据的堆栈。 对于这个示例,我们说它是新闻文章。 这些新文章必须继承自NSManagedObject才能在Core Data中使用。 现在,如果我们的数据层将NSManagedObjects返回到我们应用程序的其余部分,那么我们现在在Core Data和我们应用程序中的其余文件之间就具有依赖关系。 例如,如果我们希望移至Realm,或切换为使用其他形式的数据存储,则需要修改应用程序中的所有文件。 此示例中的应用程序很小,请想象必须为更大的应用程序执行该操作!

(Domain Objects and the Repository)

This is where Domain Objects come in. Domain Objects are value objects that are defined by your application. Rather than using objects and structures defined outside of the app, we define what we want the objects to look like. It’s then up to the repository to map between the data storage object / structure to these value objects.

这是域对象进入的地方。域对象是由应用程序定义的值对象。 而不是使用在应用程序外部定义的对象和结构,而是定义我们希望对象的外观。 然后由存储库在数据存储对象/结构与这些值对象之间进行映射。

When we do this, it means any changes to the data access layer, as we discussed earlier such as data structure changes or changes in provider don’t impact the rest of the app. The only part of the app that needs to be updated is the repository and it’s mapping to the domain objects.

当我们这样做时,它意味着对数据访问层的任何更改,如我们之前讨论的,例如数据结构更改或提供程序中的更改不会影响应用程序的其余部分。 应用程序唯一需要更新的部分是存储库,它正在映射到域对象。

The below quote summarises the idea of the pattern:

下面的引用总结了该模式的概念:

Repositories are classes or components that encapsulate the logic required to access data sources. They centralize common data access functionality, providing better maintainability and decoupling the infrastructure or technology used to access databases from the domain model layer.

存储库是封装访问数据源所需逻辑的类或组件。 它们集中了通用的数据访问功能,从而提供了更好的可维护性,并使用于从域模型层访问数据库的基础架构或技术脱钩。

Let’s have a look at our previous example but modified to use the a repository and domain objects:

让我们看一下我们前面的示例,但将其修改为使用存储库和域对象:

So what is the difference here? As you can see the Core Data stack is still returning NSManagedObjects, however the repository is converting that to a domain object. This object doesn’t inherit from NSManagedObject, also it’s structure and attributes are defined by the app rather than what is in the data store.

那么这里有什么区别? 如您所见,Core Data堆栈仍在返回NSManagedObjects,但是存储库正在将其转换为域对象。 该对象不继承自NSManagedObject,它的结构和属性由应用程序定义,而不是由数据存储区定义。

Now if we wanted to move away from Core Data to something else the only classes that need to be changed are the Core Data stack and the repository. The rest of the app does not need to be changed as we can map the new data stores type to our domain objects using the repository.

现在,如果我们要从核心数据转移到其他内容,则唯一需要更改的类是核心数据堆栈和存储库。 该应用程序的其余部分无需更改,因为我们可以使用存储库将新的数据存储类型映射到我们的域对象。

(Example)

To show a small working example we are going to use a couple of Free Public APIs (highly recommend this resource if you are looking to build a demo app or experiment). We will use 2 APIs that returns users. However they return them in a different format.

为了显示一个小的工作示例,我们将使用几个免费的公共API (如果您要构建演示应用程序或实验,强烈建议使用此资源)。 我们将使用2个返回用户的API。 但是,它们以不同的格式返回它们。

https://jsonplaceholder.typicode.com/users/1

https://jsonplaceholder.typicode.com/users/1

https://randomuser.me/api/

https://randomuser.me/api/

As we have done in previous blog posts we are going to use QuickType to generate our Codable objects from our JSON response. We will start with our first request.

正如我们在先前的博客文章中所做的那样,我们将使用QuickType从JSON响应中生成我们的Codable对象。 我们将从第一个请求开始。

This structure will allow us to decode the response from the first request. Let’s make a simple example that takes the response and outputs some data. We will be using code from our Simple JSON Decoder to process the output so feel free to read up if the code you see doesn’t make sense.

这种结构将使我们能够解码来自第一个请求的响应。 让我们做一个简单的示例,该示例接受响应并输出一些数据。 我们将使用来自简单JSON解码器的代码来处理输出,因此如果您看到的代码没有意义,请随时阅读。

So let’s step through what’s happening here:

因此,让我们逐步了解一下这里发生的情况:

  1. First of all we are making the request using our Simple JSON Decoder to return our new User type. 首先,我们使用简单JSON解码器发出请求以返回新的User类型。
  2. Output any errors
  3. So here we are outputting the name, address and location of the user we get back. Super simple right now.

(Managing change)

Now let’s say we change provider. Maybe our backend team changes the API, or we switch data provider or from 2 different data provider SDKs. In our example we will switch from the first url ( https://jsonplaceholder.typicode.com/users/1) to the second ( https://randomuser.me/api/).

现在,我们更改供应商。 也许我们的后端团队更改了API,或者我们切换了数据提供程序,或者从2个不同的数据提供程序SDK中进行了切换。 在我们的示例中,我们将从第一个URL( https://jsonplaceholder.typicode.com/users/1 )切换到第二个URL( https://randomuser.me/api/ )。

The first thing we will need to do is change all of our codable objects as the structure of the response is different. Let’s use QuickType again to give us the new structure:

我们需要做的第一件事是更改所有可编码对象,因为响应的结构不同。 让我们再次使用QuickType为我们提供新的结构:

Now this is more complicated that it needs to be for our example but I’m leaving it here as an extreme example of how different things can be. As you can probably tell the structure and types have change dramatically from our first example. So let’s try and output the same data from this example in our previous example. We can ignore the request part and just focus on the data output so we can see the differences:

现在,这变得更复杂了,需要以我们的示例为例,但我将其留在这里,以作为一个不同示例的极端示例。 如您所知,与第一个示例相比,结构和类型已发生了巨大变化。 因此,让我们尝试在上一个示例中输出与该示例相同的数据。 我们可以忽略请求部分,而只关注数据输出,因此我们可以看到不同之处:

As you can see from even this simple example. We would have to change 7 lines of code, just to produce the same output. Now imagine this change happening on a much bigger project! There could possibly be 100s of lines of code that would need updating, all because the API response has changed.

您甚至可以从这个简单的示例中看到。 为了产生相同的输出,我们必须更改7行代码。 现在想象一下,这个变化发生在一个更大的项目上! 由于API响应已更改,可能需要数百行代码进行更新。

(Repository Pattern)

Here is where the repository pattern comes in. We can create a user repository that fetches the user and converts it to our domain object. That way we don’t need to update the output.

这就是存储库模式的用处。我们可以创建一个用户存储库,以提取用户并将其转换为我们的域对象。 这样,我们就不需要更新输出。

First thing to do is design our domain object that will represent a User in our system. Now all we are doing in this simple example is outputting a few attributes so let’s design our object with just those attributes as we don’t need the rest.

首先要做的是设计我们的域对象,该对象将代表我们系统中的一个用户。 现在,在这个简单的示例中我们要做的就是输出一些属性,因此让我们仅使用那些属性来设计对象,因为我们不需要其余的属性。

Here we have a nice simple representation of our User object. There is no need to consider any of the other possible attributes returned from the API. We aren’t using them in our application and they will just sit around taking up valuable memory. You will also notice that this object doesn’t conform to Codable or subclass NSManagedObject. This is because DomainObject should not contain any knowledge about how they are stored. That is the responsibility of the repository.

在这里,我们可以很好地表示User对象。 无需考虑从API返回的任何其他可能的属性。 我们不在应用程序中使用它们,它们只会占用宝贵的内存。 您还将注意到,该对象不符合Codable或子类NSManagedObject。 这是因为DomainObject不应包含有关其存储方式的任何知识。 这是存储库的责任。

To design our repository we can make use of Generics and Protocols to design a repository we can use for anything, not just our DomainUser. Let take a look:

为了设计我们的存储库,我们可以使用泛型和协议来设计一个我们可以用于任何事物的存储库,而不仅仅是我们的DomainUser。 让我们来看看:

Here we have different functions for all of the operations we can do. What you will notice is that none of these functions specify where or how the data is stored. Remember when we talked about different storage options at the beginning? We could implement a repo that talks to an API (like in our example), one that stores things in Core Data or one that writes to UserDefaults. It’s up to the repository that implements the protocol to decide these details, all we care about is that we can load and save the data from somewhere.

在这里,我们可以执行的所有操作都有不同的功能。 您会注意到,这些功能都没有指定数据的存储位置或存储方式。 还记得当我们在一开始谈论不同的存储选项时吗? 我们可以实现一个与API通信的存储库(例如在我们的示例中),一个将内容存储在Core Data中的存储库,或者一个写入UserDefaults的存储库。 这取决于实现协议的存储库来决定这些细节,我们所关心的只是可以从某处加载和保存数据。

(See it action)

Now we have defined what the repository pattern is, let’s create 2 implementations. One for our first request and one for the second. Both should return domain objects, rather than the type returned from the request.

现在我们定义了存储库模式是什么,让我们创建2个实现。 一个是我们的第一个请求,另一个是第二个。 两者都应返回域对象,而不是从请求返回的类型。

There’s quite a bit of code here so let’s step through it.

这里有很多代码,所以让我们逐步进行。

  1. First of all we have defined a new error to send back if we don’t receive any user info from the API.
  2. This is the same call we made in our example before.
  3. Now here we are taking the returned Codable User and converting it to your new DomainUser class.
  4. We aren’t implementing the other functions in this example so just leaving them empty for now to remove errors.
  5. This struct is the second request we are making, and again here we are mapping our Users Codable type from the second request to our DomainUser.

Now that we have made our two repositories, let’s show how we can quickly switch between them without breaking / changing code.

现在我们已经创建了两个存储库,让我们展示如何在不破坏/更改代码的情况下快速在它们之间切换。

Here is our example from earlier in the article but updated to use our new repositories. Here we go and fetch the user and print their details, the same as before. Now below we can switch to our second request and see how that will work.

这是本文前面的示例,但已更新为使用我们的新存储库。 与以前一样,我们在这里获取用户并打印其详细信息。 现在,在下面,我们可以切换到第二个请求,看看它如何工作。

Now notice how the only part we changed was the implementation class? The rest of the code remained the same even though where the data was coming from has changed and is coming back in a completely different structure. Now imagine we are using this repo in many places to fetch user details. We can quickly switch between different data sources without changing the code that uses it. The only changes we have to make are to the repo and to the data mapping code. So only one change rather than a change in every single class that uses these objects.

现在注意,我们更改的唯一部分是实现类吗? 即使数据来自何处已更改并以完全不同的结构返回,其余代码仍保持不变。 现在,假设我们在许多地方都使用此仓库来获取用户详细信息。 我们可以在不同的数据源之间快速切换,而无需更改使用它的代码。 我们要做的唯一更改就是对仓库和数据映射代码的更改。 因此,只有一个更改而不是使用这些对象的每个类的更改。

(Conclusion)

So let’s recap what we have discussed here:

因此,让我们回顾一下我们在这里讨论的内容:

  • First of all we discussed the problem of using data storage classes throughout your codebase. Especially on large projects if you need to switch data source / structure.
  • We then discussed how using the repository pattern and mapping to domain objects rather than using data storage classes can make your code easier to change in the future.
  • We worked through some examples of how changing API structures can impact your code.
  • We then implemented a basic repository pattern with mapping to domain objects to show how doing this can make updating your project easier.

Finally let’s discuss the pros and cons of the approach:

最后,让我们讨论一下这种方法的利弊:

(Advantages)

  • Code is easier to change if you need to switch data source or structure
  • Separates concerns of where / how data is stored away from the rest of your app

(Disadvantages)

  • Adds more code and complexity
  • Need to write mappers for each object to domain objects
  • Not really needed on smaller solo projects

Feel free to download the playground and play around with the examples yourself

可以免费下载游乐场并自己玩示例

Originally published at https://pyartez.github.io on July 19, 2020.

最初于 2020年7月19日 发布在 https://pyartez.github.io 。

翻译自: https://medium.com/dev-genius/repository-pattern-in-swift-a8eda25b515d

swift 听筒模式