Swift -创建一个具有协议类型属性的结构体

qgzx9mmu  于 2023-04-28  发布在  Swift
关注(0)|答案(1)|浏览(104)

我有一个结构,需要解码和散列。此结构具有Protocol类型的属性。根据类型,在结构中填充协议的具体值。但是,我如何使这个结构可散列化,而不使协议可散列化(这使它成为一个不能直接使用的通用协议)?

enum Role: String, Decodable, Hashable {
 case developer
 case manager
  ....
}

protocol Employee {
 var name: String { get }
 var jobTitle: String { get }
 var role: Role { get }
}

struct Manager: Employee, Hashable {
 let name: String
 let jobTitle: String
 let role: Role = .manager
  ....
}

struct Developer: Employee, Hashable {
 let name: String
 let jobTitle: String
 let role: Role = .developer
 let manager: Employee  // Here is the problem
 
 static func == (lhs: Developer, rhs: Developer) -> Bool {
  lhs.name == rhs.name &&
  lhs.jobTitle == rhs.jobTitle &&
  lhs.role == rhs.role &&
  lhs.manager == rhs.manager // Type 'any Employee' cannot conform to 'Equatable'
 }
 
 func hash(into hasher: inout Hasher) {
  hasher.combine(name)
  hasher.combine(jobTitle)
  hasher.combine(role)
  hasher.combine(manager) // Instance method 'combine' requires that 'H' conform to 'Hashable'
 }
}

这有多个问题:
1.为了使一个属性Hashable/Equatable,我们需要编写包含所有属性的==hash函数。
1.即使我们这样做了,仍然有一个问题,即协议不是Hashable/Equatable。
有没有其他或正确的方法来做到这一点?

b1zrtrql

b1zrtrql1#

我的意思是,你已经做了这个内部。你有一个枚举,它与结构体有1:1的对应关系。这表明枚举应该包含数据而不是使用协议:

indirect enum Employee: Decodable, Hashable {
    case developer(Developer)
    case manager(Manager)

    var name: String {
        switch self {
        case .developer(let developer): return developer.name
        case .manager(let manager): return manager.name
        }
    }
    var jobTitle: String {
        switch self {
        case .developer(let developer): return developer.jobTitle
        case .manager(let manager): return manager.jobTitle
        }
    }
}

struct Manager: Decodable, Hashable {
    let name: String
    let jobTitle: String
}

struct Developer: Decodable, Hashable {
    let name: String
    let jobTitle: String
    let manager: Employee
}

这有点乏味,因为每个属性在每种情况下都需要一个switch。如果属性的数量有限,这很好,但是如果属性列表很大并且不断增长,这可能会很烦人。
由于员工都非常相似,另一种方法是将不同的内容放入他们的角色中。(我假设这是一个通用的例子,因为在大多数组织中,经理也有经理。这可能是一个重新思考你的类型的时候,并决定经理和开发人员是否真的是不同的东西。如果developer.manager可以成为开发者,为什么是两种类型?)

indirect enum Role: Decodable, Hashable {
    case developer(Developer)
    case manager(Manager)
}

struct Employee: Decodable, Hashable {
    // all the same things
    var name: String
    var jobTitle: String

    // a bundle of all the special things
    var role: Role
}

struct Manager: Decodable, Hashable {}

struct Developer: Decodable, Hashable {
    let manager: Employee
}

但您也可以使用协议保留当前的设计。在您的示例中,不需要any Employee成为Equatable。您需要能够将一个Employee与任意其他Employee进行比较。那不是一回事
以这种方式扩展您的Employee,允许它与任意其他Employee进行比较(这与Equatable不同,Equatable只是意味着一个类型可以与Self进行比较)。

protocol Employee {
    var name: String { get }
    var jobTitle: String { get }
    var role: Role { get }
    func isEqual(to: any Employee) -> Bool
}

extension Employee where Self: Equatable {
    func isEqual(to other: any Employee) -> Bool {
        guard let other = other as? Self else {
            return false
        }
        return self == other
    }
}

这样,您的==实现看起来像:

static func == (lhs: Developer, rhs: Developer) -> Bool {
    lhs.name == rhs.name &&
    lhs.jobTitle == rhs.jobTitle &&
    lhs.role == rhs.role &&
    lhs.manager.isEqual(to: rhs.manager)
}

Hashable的情况甚至更简单,因为Hashable没有自己的关联类型。所以你可以把它作为一个要求:

struct Developer: Employee, Hashable {
    ...
    let manager: any Employee & Hashable
    ...

相关问题