尚不清楚您打算将此存储库用于什么目的。 Repository 专为具有某些特征(例如与行式数据库的强链接)的环境而设计。我将讨论在常见 iOS 应用(甚至更小的 Mac 应用)中通常更有效的其他模式。
我通常非常不喜欢类型擦除,这通常表明存在设计问题。但在这种情况下,我认为类型擦除可能是一个合理的答案。
因此,我们将从可以存储的物品种类开始。它们可能需要某种标识符,并且对于许多常见的后端都是可散列的(但也许您不需要散列;如果不需要,请将其取出)。
protocol Identified {
associatedtype ID
var id: ID { get }
}
typealias Storable = Identified & Hashable
还有一些可以作为存储的东西。没有“RepositoryStorage”这样的东西。这只是说“如果您遵守这些要求,那么 Repository 可以使用您。”
protocol RepositoryStorage {
associatedtype Item: Storable
func get(identifier: Item.ID) -> Item?
func add(item: Item)
func delete(identifier: Item.ID)
func allItems() -> [Item]
}
然后是标准的、有点乏味的类型橡皮擦模式(stdlib 使用的另一种模式更乏味,但这个模式在大多数情况下已经足够好了)。
// I'm making it a class because I assume it'll have reference semantics.
final class Respository<Item: Storable>: RepositoryStorage {
init<Storage: RepositoryStorage>(storage: Storage) where Storage.Item == Item {
self._get = storage.get
self._add = storage.add
self._delete = storage.delete
self._allItems = storage.allItems
}
let _get: (Item.ID) -> Item?
func get(identifier: Item.ID) -> Item? { return _get(identifier) }
let _add: (Item) -> Void
func add(item: Item) { _add(item) }
let _delete: (Item.ID) -> Void
func delete(identifier: Item.ID) { _delete(identifier) }
let _allItems: () -> [Item]
func allItems() -> [Item] { return _allItems() }
}
没关系,它是一个通用存储库。如果您正在处理可能将存储在 SQLite 数据库中的大量项目,这是合理的。但根据我的经验,它往往既太多又太少。如果它只是几个项目,那就太多了,如果你有很多项目,那就太少了,所以可能需要做的不仅仅是 CRUD。您可能需要 Query 和 Join,然后这还不够。 (使某些东西在一个方向上变得灵活通常会在其他方向上使您不适应。没有通用的“通用”。)
那么,当它实际上只有几件物品时,我们是否可以让它变得更简单?这是我经常使用的一种方法:
class DataStore<Key: Hashable & Codable, Value: Codable> {
let identifier: String
private(set) var storage: DataStorage
var dictionary: [Key: Value] {
didSet {
storage[identifier] = try? PropertyListEncoder().encode(dictionary)
}
}
init(identifier: String, storage: DataStorage = UserDefaults.standard) {
self.identifier = identifier
self.storage = storage
let data = storage[identifier] ?? Data()
self.dictionary = (try? PropertyListDecoder().decode([Key: Value].self,
from: data)) ?? [:]
}
subscript(key: Key) -> Value? {
get { return dictionary[key] }
set { dictionary[key] = newValue }
}
}
DataStore 充当字典,您可以在其中存储键/值对:
let ds = DataStore<String: Item>(identifier: "Item")
ds["first"] = item
它可以存储任何可编码的东西。稍加修改,您就可以将其从类字典接口切换为类数组或类集接口;我通常只是想要一本字典。
当它被更新时,它会将整个数据存储编码到它的存储中作为数据:
protocol DataStorage {
subscript(identifier: String) -> Data? { get set }
}
这对于几十个项目来说非常快速和高效。我可能会重新考虑如果有超过一百个项目,那么对于数百个或更多项目是不合适的。但对于小型集,这非常非常快。
一个很常见的DataStorage是UserDefaults:
extension UserDefaults: DataStorage {
subscript(identifier: String) -> Data? {
get { return data(forKey: identifier) }
set { set(newValue, forKey: identifier) }
}
}
关键的教训是,通过创建低层专用的通用货币(数据),这消除了所有类型擦除跳跃。只要你能做到这一点,将上层的泛型接口与下层的非泛型接口分开,你就会省去很多乏味的事情。
这可能适用于您的情况,也可能不适用。它是为键/值存储而不是数据库量身定制的,而且它的设计目的是阅读频率高于写入频率。但是对于这种用法,它比存储库模式更简单且通常更快。当我说了解你的用例很重要时,我的意思就是这种权衡。