仓库模式及其在Swift 项目中的应用详解

   2023-02-07 学习力0
核心提示:目录正文不使用仓库模式时的代码使用仓库有什么好处?设计仓库接口实现仓库接口选择用哪个仓库实现处理数据源的变更总结正文在现代 Swift 项目中,很流行一种模式叫做仓库模式,英文是 Repository Pattern。这个模式主要用于构建数据层代码。按照一般的 App

正文

在现代 Swift 项目中,很流行一种模式叫做仓库模式,英文是 Repository Pattern。这个模式主要用于构建数据层代码。按照一般的 App 层级划分,一般从上到下划分为 UI 层,业务层,数据层,那么仓库模式的应用位置可以参考下图:

仓库模式及其在Swift 项目中的应用详解

可以看到仓库应用在数据层,业务层通过接口来访问仓库。

不使用仓库模式时的代码

为了研究为什么要使用仓库模式,我们先看看不使用仓库模式时我们是怎么写代码的。一般打开一个界面,会发送网络请求来获取这个界面所需的数据,这时会在 ViewController 写类似下面的代码:

func viewDidLoad() {
    super.viewDidLoad()
    requestData()
}
func requestData() {
    API.request(xxxId: 12345) { result in
        switch result {
        case .success(let model):
            handle(model)
            DispatchQueue.main.async {
                render(model)
            }
        case .failure(let error):
            print(error.localizedString)
        }
    }
}

这种写法在小项目中是没有问题的,但是在稍具规模的项目中,就会对项目的扩展性,维护性,团队合作,开发效率有比较高的要求,这时候就更应该根据科学的软件设计原则来设计更好的架构。

上面的代码在稍具规模的项目中会有以下的缺点:

  • 数据访问代码写在了 ViewController 中,无法测试
  • ViewController 会过于臃肿,难以维护
  • 如果数据访问方式修改了,需要修改 ViewController 中的代码

稍具规模的项目一般会采用 MVVM 等架构,于是以上的代码会写在 ViewModel 中,来避免 ViewController 太过臃肿,但是还是会有无法测试和修改数据访问方式时改动比较大的问题。

使用仓库有什么好处?

总的来说,就是能提供高层次的抽象,从而获得因为抽象带来的一系列好处。

仓库模式提供了数据层的抽象,可以让你的业务代码只依赖一个简单的抽象接口就可以工作。这使得代码松耦合,业务代码不需要知道数据的具体获取和存储细节。

仓库模式让代码可以测试,对于网络请求和数据库读写的部分,可以实现一个提供测试数据的仓库实例,这样就可以编写相应的测试代码。

下面就来看一看怎样使用仓库模式。

设计仓库接口

按照依赖倒转原则,数据访问层作为架构中的一个整体,上层对象在调用数据访问层时应该依赖接口,而不依赖于实现,这样数据访问层的逻辑就可以灵活的变更、替换,比如在网络请求和本地数据之间切换,在不同的网络请求协议之间切换等。

所以设计仓库模式应该先定义接口,定义接口有两种方式,一种是根据具体的数据定义特定的仓库接口,例如对于一个新闻列表的接口:

protocol NewsListRepository {
    func readNewsList() async throws -> [News] 
}

或者是根据不同的业务数据的访问逻辑其实大同小异,可以设计统一的泛型接口:

protocol Repository {
    associatedtype T
    func query(with predicate: NSPredicate?, sortDescriptors: [NSSortDescriptor]?) async throws -> [T]
    func save(entity: T) async throws
    func delete(entity: T) async throws
}

实现仓库接口

我们一般会根据数据的存放方式来定义不同的仓库实现,比如对于网络请求的数据,定义一种仓库的实现,对于本地数据库中存放的数据定义一种仓库的实现,也可以定义一种假数据仓库来编写测试代码。

比如对于网络请求的数据,可以定义一个如下的仓库实现:

class DefaultNewsListRepository: NewsListRepository {
    let remoteDataSource: NewsListRemoteAPI
    init(remoteDataSource: NewsListRemoteDataSource) {
        self.remoteDataSource = remoteDataSource
    }
    func readNewsList() async throws -> [News] {
        return remoteDataSource.requestNewsList();
    }
}

对于本地数据,可以设计一个如下的仓库实现:

class DatabaseNewsListRepository: NewsListRepository {
    let newsListDataStore: NewsListDataStore
    init(newsListRemoteAPI: NewsListRemoteAPI) {
        self.newsListRemoteAPI = newsListRemoteAPI
    }
    func readNewsList() async throws -> [News] {
        return newsListRemoteAPI.requestNewsList();
    }
}

为了编写测试代码,可以提供一个假数据的仓库实现:

class FakeNewsListRepository: NewsListRepository {
    func readNewsList() async throws -> [News] {
        return [
            News(),
            News(),
            News()
        ]
    }
}

如果需要从接口请求到数据后放入本地数据库缓存,然后从本地数据库中读取数据渲染在界面上,也可以用一个仓库搞定。

class DefaultNewsListRepository: NewsListRepository {
    let newsListRemoteAPI: NewsListRemoteAPI
    let newsListDataStore: NewsListDataStore
    init(newsListRemoteAPI: NewsListRemoteAPI, newsListDataStore: NewsListDataStore) {
        self.newsListRemoteAPI = newsListRemoteAPI
        self.newsListDataStore = newsListDataStore
    }
    func readNewsList() async throws -> [News] {
        var newsList = newsListDataStore.readNewsList()
        if newsList.count == 0 {
            let news = newsListRemoteAPI.requestNewsList()
            newsListDataStore.save(newsList)
            newsList = news
        }   
        return newsList
    }
}

选择用哪个仓库实现

在现代 App 项目中,一般会用 MVVM 等架构来组织代码。这里以 MVVM 为例,ViewModel 会依赖仓库接口来存取数据。

class NewsListViewModel: ViewModel {
    let newsListRepository: NewsListRepository
    init(newsListRepository: NewsListRepositoy) {
        self.newsListRepository = newsListRepository
    }
}

为了提高代码的维护性和扩展性,最好使用依赖注入的方式来给 ViewModel 注入 Repository 的依赖,这样可以方便得替换仓库的实现而不用修改 ViewModel 的代码。

可以在创建 ViewModel 时创建对应的仓库对象,也可以使用依赖注入容器。

init(newsListRepository: NewsListRepository = DIContainer.shared.resolve(NewsListRepository.self)) {
    self.newsListRepository = newsListRepository
}

处理数据源的变更

当遇到需要变更数据源的时候,例如本地数据库从 CoreData 切换到 SQLite 或 Realm。或者更换了网络库,从 NSURLSession 换成 Alamofire,这些情况仓库模式就能发挥它的优势。无需修改业务方代码,只需要替换成一种新的仓库实现即可。

还有另一种情况,就是对于同一个业务,后端协议变更了。如果使用了仓库模式,也可以很方便的进行代码调整。

通常,业务层会有一个业务模型,比如对于用户的信息,在业务层定义了一套模型:

struct DomainUser {
    let name: String
    let age: Int
    let nickname: String
}

原本通过接口返回的 json 字符串:

{ 
    "name": "zhangsan",
    "age": 20,
    "nickname": "xiaozhang"
}

可以直接通过解析 JSON 然后构造出 DomainUser 对象,但是突然某一天后端说要技术调整,迁移到新的接口,新接口返回的结构和以前不一样了。

如果没有用仓库模式,业务方直接依赖具体的数据模型,如果接口结构调整了,那么所有的业务调用方的代码都要调整。

使用了仓库模式,业务方依赖于仓库,仓库可以在获取到数据结构后将它转为业务方需要的数据模型,这样无论后端协议怎么变更,都可以仅在数据层增加一种新的仓库实现,不需要改动业务方代码。这遵守了开放-封闭程序设计原则。

总结

在稍具规模的项目中使用仓库模式,可以让代码抽象度更高,耦合度更低,方便扩展和维护,可以编写测试代码,在大型项目中,可以方便的实现数据源和数据结构的切换。仓库模式也将数据存储的细节和程序的其它部分分离开,使得职责更清晰。

仓库模式也有一些缺点:为了实现这一模式需要编写更多的代码,增加了代码复杂性。需要编写映射代码来讲数据映射为业务模型。如果是小型的项目就不需要使用仓库模式了。

参考资料

以上就是仓库模式及其在Swift 项目中的应用详解的详细内容,更多关于Swift 项目仓库模式的资料请关注其它相关文章!

原文地址:https://juejin.cn/post/7186629731918446653
 
标签: Swift 仓库模式
反对 0举报 0 评论 0
 

免责声明:本文仅代表作者个人观点,与乐学笔记(本网)无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
    本网站有部分内容均转载自其它媒体,转载目的在于传递更多信息,并不代表本网赞同其观点和对其真实性负责,若因作品内容、知识产权、版权和其他问题,请及时提供相关证明等材料并与我们留言联系,本网站将在规定时间内给予删除等相关处理.

  • swift 命令行工具初探
    亲爱的同学们好,今天我们要介绍这么一个东西。相信有过解释型语言(PHP,Ruby,等)使用经验的同学会更加熟悉,就是 Swift 也为我们提供了命令行运行工具,俗称 REPL。好了,我们进入正题,在安装好 Swift 开发环境的机器上,打开命令行,输入 swift 命令,就进
    03-16
  • [Swift]冒泡排序 | Bubble sort
    [Swift]冒泡排序 | Bubble sort
    ★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★★➤微信公众号:山青咏芝(shanqingyongzhi)➤博客园地址:山青咏芝(https://www.cnblogs.com/strengthen/)➤GitHub地址:https://github.com/strengthen/LeetCode➤原文
    03-08
  • [Swift] 自定义在 SwiftUI 中实现的可搜索的外观
    [Swift] 自定义在 SwiftUI 中实现的可搜索的外
    首先我找遍了,似乎找不到任何信息......(我遇到了许多根本不起作用的事情......)终于在详细的英文文献中找到了,我会保留下来,希望以后有机会。关于 SwiftUI 中的可搜索searchable是iOS15新增的易于实现的搜索字段。关于这种情况有一个参数placement,您
    03-08
  • [Swift] 检测 SwiftUI ScrollView 中的偏移量
    [Swift] 检测 SwiftUI ScrollView 中的偏移量
    首先你可以用ScrollViewReader做一些可以从iOS14使用的事情。但是,我不能做我想做的事情,所以我想我还能做些什么。再次,可重复使用我尝试过了。执行我将首先发布实现的图像。 (Swift Playgrounds 演示)您可以像这样根据滚动获取坐标。让我们看看实现。1.
    03-08
  • 什么是 LLVM?Swift, Rust, Clang 等语言背后的原力
    什么是 LLVM?Swift, Rust, Clang 等语言背后的
    点击上方“iOS开发”,选择“置顶公众号”关键时刻,第一时间送达!创造新的语言,变着花样的提升现有语言的能力,这在整个编程界正风行。Mozilla 的 Rust、Apple 的 Swift、Jetbrains 的 Kotlin,以及许多其它的语言都给开发者在速度、安全性、便利性、可移
    02-09
  • Swift3.0 UICollectionView 删除,拖动
    Swift3.0 UICollectionView 删除,拖动
    UICollectionView实现了一下常见的新闻分类.  附有效果图 近期一直在深入学习swift,实现了CollectionView item的头东与删除,用的都是系统的一些函数方法,看起来比较直观. 第一步:class HotViewController: UIViewController,UICollectionViewDelegate,UICo
    02-09
  • swift -懒加载创建view
     // 只有外界访问到headerView的时候才会去执行闭包, 然后将闭包的返回值赋值给headerView    // 注意: 一定要记住闭包后面需要写上(), 代表执行闭包    //懒加载创建UIView    lazy var headerView: UIView = {        let view = UIView()
    02-09
  • Swift--非常好用的适合局部的代码约束
    // 哪个控件的哪个属性等于(大于、小于)另外一个控件的哪个属性乘以多少再加上多少 eg:let widthContraint = NSLayoutConstraint(item: messageLabel, attribute: NSLayoutAttribute.Width, relatedBy: NSLayoutRelation.Equal, toItem: nil, attribute: NSLa
    02-09
  • iOS打包framework - Swift完整项目打包Framework,嵌入OC项目使用
    iOS打包framework - Swift完整项目打包Framewor
    场景说明:-之前做的App,使用Swift框架语言,混合编程,内含少部分OC代码。-需要App整体功能打包成静态库,完整移植到另一个App使用,该App使用OC。-所以涉及到一个语言互转的处理,以及一些AppDelegate的代码减除变化。 --------------------------------
    02-09
  • Swift -- 官方文档Swift-Guides的学习笔记
    在经历的一段时间的郁闷之后,我发现感情都是虚伪的,只有代码是真实的(呸)因为看了swift语法之后依然不会用swift,然后我非常作死的跑去看官方文档,就是xcode里自带的help》documentation and API reference其中的swift里的guide这里主要总结一下里面每一
    02-09
点击排行