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'
}
}
С этим связано несколько проблем:
- Чтобы сделать одно свойство Hashable/Equatable, нам нужно написать
==
иhash
функция со всеми свойствами. - Несмотря на то, что мы это делаем, все еще остается проблема, когда протокол не является хешируемым/приравниваемым.
Есть ли другой или правильный способ сделать это?
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
...