Swift – создание хэшируемой структуры, имеющей свойство типа протокола

У меня есть структура, которая должна быть декодируемой и хешируемой. Эта структура имеет свойство типа Протокол. В зависимости от типа в структуру заполняется конкретное значение протокола. Но как мне сделать эту структуру хешируемой, не делая хэшируемым протокол (что делает его универсальным протоколом, который нельзя использовать напрямую)?

      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функция со всеми свойствами.
  2. Несмотря на то, что мы это делаем, все еще остается проблема, когда протокол не является хешируемым/приравниваемым.

Есть ли другой или правильный способ сделать это?

1 ответ

ИМХО, ты сделал это наизнанку. У вас есть перечисление, которое соответствует структурам 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
}

Это немного утомительно, потому что каждому объекту недвижимости требуетсяпо каждому делу. Если количество свойств ограничено, это нормально, но если список свойств большой и растет, это может раздражать.

Поскольку все сотрудники очень похожи, другой подход заключается в том, чтобы просто включить в их роли то, что варьируется. (Я предполагаю, что это общий пример, поскольку в большинстве организаций у менеджеров также есть менеджеры. Возможно, сейчас самое время переосмыслить ваши типы и решить, действительно ли менеджеры и разработчики — это разные вещи. Еслиможно быть Разработчиком, почему два типа?)

      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
}

Но вы также можете сохранить текущий проект с помощью протокола. В вашем примере вам не нужнобыть равноправным. Вы должны иметь возможность сравнить Сотрудника с произвольным другим Сотрудником. Это не одно и то же.

Расширьте своего сотрудника таким образом, чтобы его можно было сравнивать с произвольными другими сотрудниками (что не то же самое, что 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
    ...
Другие вопросы по тегам