我正在重做一个模型库,希望比我聪明的人可以帮助我解决我之前实现中的一个烦恼。
假设我有一系列这样的相关模型:
class Pond {
let pk: Int
let depth: Int
}
class Frog {
let pk: Int
let name: String
let pond: Pond
}
对象是从商店加载的。假设我捡到一只 Frog 。它有一个主键,它属于一个池塘。我会得到这样的 JSON:
{"pk": 42, "name": "Kermit", "pond_pk": 1}
所以,我知道 Frog 的一切,我知道它属于哪个池塘,但我不知道(也不需要知道)那个池塘的深度。这意味着我还不能实例化 Pond 对象。
以下是我考虑过的解决方案:
1) 将每个关系属性加倍。如:
class Frog {
let pond: Pond?
let pondPk: Int
}
这看起来一点都不好。
2) 与上面类似,但是创建一个 Optional-ish 结构来处理关系。
struct Relationship {
let pk: Int
let object: AnyObject?
}
class Frog {
let pond: Relationship
}
然后测试 frog.pond.object
但这非常冗长。
3) 用“ stub ”版本将每个类加倍,这样关系就可以是 PondStub 或 Pond(如果已满载)。
class PondStub {
let pk: Int
}
class Pond: PondStub {
let depth: Int
}
class FrogStub {
let pk: Int
}
class Frog: FrogStub {
let name: String
let pond: PondStub
}
4) 放弃并将每个非 pk
属性实现为隐式展开的 Optional 并在我不确定是否已加载相关项时检查 nil
对象。
class Pond {
let pk: Int
let depth: Int!
}
这是我自 Swift 1 以来所做的事情。它奏效了,只是我失去了 Optional 类的所有好处。这就像我在与 Swift 作斗争,而不是正确地使用它。
任何更好的想法将不胜感激。
PS:这将用于 Linux 上的 Swift。
编辑:我一夜之间有了另一个想法,下面 dfri 的评论对此表示赞同(这应该是一个答案!)我喜欢它的一切,除了很难模拟对象之间的关系,如果它们不是' 保存到 Store(例如,在创建新对象时)。
// The model objects are still very simple.
struct Pond {
let pk: Int?
let depth: Int
}
struct Frog {
let pk: Int?
let name: String
let pondPk: Int?
}
// There is a separate Store module which co-ordinates the objects
// and their relationships.
protocol Storable {
static var hash: String { get }
var pk: Int? { get }
}
extension Storable {
var hash: String? {
get {
guard let pk = self.pk else { return nil }
return self.dynamicType.hash + String(pk)
}
}
}
class Store {
var contents: [String: Storable] = [:]
func add(obj: Storable) {
guard let hash = obj.hash else { return }
contents[hash] = obj
}
func fetch(type: Storable.Type, withPk pk: Int) -> Storable? {
let hash = type.hash + String(pk)
return contents[hash]
}
}
// This module also handles easy lookups between the models.
extension Pond: Storable {
static let hash = "Pond"
}
extension Frog: Storable {
static let hash = "Frog"
func pond(fromStore store: Store) -> Pond? {
guard let pk = pondPk else { return nil }
return store.fetch(Pond.self, withPk: pk) as? Pond
}
}
// Example usage.
let store = Store()
let pond = Pond(pk: 1, depth: 4)
store.add(pond)
let frog = Frog(pk: 1, name: "Kermit", pondPk: pond.pk!)
store.add(frog)
if let fetchedPond = frog.pond(fromStore: store) {
print(fetchedPond.depth) // prints 4
}
最佳答案
一些笔记(NX)
关于您的(好的)解决方案如下。
在上面编辑的示例用法 ( frog.pond(fromStore: store)
) 中,您使用 Frog
在 store
中具有自身副本 的实例实例。即
// Example usage.
let store = Store()
let pond = Pond(pk: 1, depth: 4) // (N1)
store.add(pond)
let frog = Frog(pk: 1, name: "Kermit", pondPk: pond.pk!)
store.add(frog)
if let fetchedPond = frog.pond(fromStore: store) { // (N2)
print(fetchedPond.depth) // prints 4
}
注意事项:
-
(N1)
: 添加pond
的副本至store
.可能更好地初始化并将池塘一步添加到store
:store.add(Pond(pk: 1, depth: 4))
. -
(N2)
: 在这里你使用一个frog
的实例store
中存在副本(赋值) ,但不使用store
中的实际副本.
即
var frog = Frog(pk: 1, name: "Kermit", pondPk: pond.pk!)
store.add(frog)
frog.name = "Hermit" // <-- will not effect Kermit frog in store
您可以让您的 Frog 成为类类型而不是值类型,或者您可以问问自己 Frog 本身是否真的需要存储(或者这是否只是池塘需要的?)。
我将添加您在上面的编辑中发布的“可存储”解决方案的变体。在此,Frog
实例不存储在中央存储中,但可以“本地”使用并访问 frog:s 关联 Pond
(存储在中央商店中)通过使用对商店的引用。根据您问题的原始细节,这是可以接受的(“需要访问 Pond
的 Frog
实例,但反之则不行”);然而,如果你一定要存储所有不同的东西,这将是一个交易破坏者 Frog
您商店中的实例。
如果 Frog
这可能会有用:s 是“动态” 对象,而池塘是“更大的静态实体”,例如;有限数量的池塘将在初始化时添加到中央商店,而 Frog
对象将在运行时动态弹出并超出范围。
商店模块
这适用于此替代解决方案以及您上面的解决方案:不需要属性 pk
是可选的,因为所有现有 Pond
或 Frog
实例应始终具有自己的 ID。
protocol Storable {
static var hash: String { get }
var pk: Int { get }
}
extension Storable {
var hash: String {
get {
return self.dynamicType.hash + String(pk)
}
}
}
class Store {
var contents: [String: Storable] = [:]
func add(obj: Storable) {
contents[obj.hash] = obj
}
func fetch(type: Storable.Type, withPk pk: Int) -> Storable? {
let hash = type.hash + String(pk)
return contents[hash]
}
}
池塘和 Frog 类
由于中央商店应该始终被初始化(即使它是空的),我们可以添加一个商店引用到您的 Frog
对象(在初始化时)。我们在只有 Pond
的替代解决方案的上下文中提出这一点:s 是可存储的,而 Frog
:s 包含对中央商店的引用,通过它,可以巧妙地访问 Pond
frog.pond?.depth
池塘的属性(通过可选链接,例如 Frog
) :s pondPk
属性(property)。
struct Pond : Storable {
static let hash = "Pond"
let pk: Int
let depth: Int
}
struct Frog {
let pk: Int
let name: String
let pondPk: Int?
weak var store: Store? // reference to central Store()
var pond: Pond? {
guard let pondPk = pondPk else { return nil }
return store?.fetch(Pond.self, withPk: pondPk) as? Pond
}
}
示例用法
func printPondDepth(frog: Frog) {
if let _ = frog.pondPk {
print(frog.pond?.depth ?? "No pond with id \(frog.pondPk ?? 0) in frog \(frog.name):s associated store")
}
else {
print("No pond id associated with frog \(frog.name)")
}
}
/* Central store */
let store = Store()
/* Some frogs read prior to any Ponds being added to the store */
let frog = Frog(pk: 1, name: "Kermit", pondPk: 1, store: store)
let anotherFrog = Frog(pk: 2, name: "Mitker", pondPk: 3, store: store)
/* Adding ponds to the store */
store.add(Pond(pk: 1, depth: 4))
store.add(Pond(pk: 4, depth: 25))
/* another frog and some attempts to get the depth of the
pond in which a frog possibly resides */
let thirdFrog = Frog(pk: 3, name: "Kerker", pondPk: nil, store: store)
printPondDepth(frog) /* 4 */
printPondDepth(anotherFrog) /* No pond with id 3 in frog Mitker:s associated store */
printPondDepth(thirdFrog) /* No pond id associated with frog Kerker */
关于swift - 在 Swift 中建模部分加载的相关对象,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/35100938/