Вызов реализации протокола по умолчанию из обычного метода, когда протокол имеет связанный тип
У меня есть протокол, который имеет статический метод с параметром по умолчанию. Я хочу изменить значение по умолчанию в классе, который реализует протокол. По сути делаю то, что легко сделать с классами и супер.
У меня есть решение, только когда у протокола нет ассоциированного типа.
Следующий код работает, но как только вы раскомментируете объявление связанного типа, он не компилируется.
protocol Protocol {
// associatedtype AssociatedType
}
extension Protocol {
func sayHello(name: String = "World") {
print("Hello, \(name)!")
}
}
class Class<T>: Protocol {
typealias AssociatedType = T
func sayHello(name: String = "Stack Overflow") {
// Uncommenting the Protocol.AssociatedType causes:
// Protocol can only be used as a generic constraint because it has associated type requirements
(self as Protocol).sayHello(name)
}
}
Class<()>().sayHello()
Я понимаю, почему он не компилируется: Protocol
не имеет конкретного типа для AssociatedType
,
Поэтому, возможно, вопрос должен гласить: "Могу ли я явно специализировать протокол?", На что я считаю, что ответ - нет.
У меня есть частичное решение. Но даже когда это работает, это отстой.
Особенно, если учесть, что я пишу библиотеку, где sayHello
является публичным, поэтому следующий обходной путь заставляет меня иметь второй протокол, который должен быть публичным, но бесполезным.
Вот обходной путь:
protocol Parent {}
protocol Protocol: Parent {
associatedtype AssociatedType
}
extension Parent {
func sayHello(name: String = "World") {
print("Hello, \(name)!")
}
}
class Class<T>: Protocol {
typealias AssociatedType = T
func sayHello(name: String = "Stack Overflow") {
(self as Parent).sayHello(name)
}
}
Class<()>().sayHello()
Но это не работает для меня, потому что мой sayHello
использует связанный тип. Так что его нельзя извлечь в другой протокол.
Просто чтобы быть уверенным, что я ясен, вот что я хотел бы, только заменяя класс протоколом:
class Protocol<T> {
func sayHello(name: String = "World") {
print("Hello, \(name)!")
}
}
class Class<T>: Protocol<T> {
override func sayHello(name: String = "Stack Overflow") {
super.sayHello(name)
}
}
Class<()>().sayHello()
2 ответа
Вы пытаетесь заново изобрести наследование в протоколах, а такого нет. Но понять, о чем ты говоришь, тривиально; просто скажи, что ты имеешь в виду. Вы не имеете в виду "Я хочу сделать то, что унаследовал". Ты имеешь в виду "Я хочу сделать обычное поведение". Просто укажите имя для этого обычного поведения. Это устраняет всю неопределенность, о которой вы имеете в виду.
protocol Protocol {
associatedtype AssociatedType
}
extension Protocol {
// Put the default behavior on the protocol, not on the instance
// Of course you could also put it on the instance if that were convenient.
static func defaultSayHello(_ name: String = "World") {
print("Hello, \(name)!")
}
// If you want a default on the instance, too, provide one that we an override
func sayHello(_ name: String = "World") {
Self.defaultSayHello(name)
}
}
class Class<T>: Protocol {
typealias AssociatedType = T
func sayHello(name: String = "Stack Overflow") {
// Now the default behavior lives on my type
Class.defaultSayHello(name)
}
}
// But other types can get default behavior
class OtherClass<T>: Protocol {
typealias AssociatedType = T
}
Class<()>().sayHello() // Hello, Stack Overflow!
OtherClass<()>().sayHello() // Hello, World!
Единственное разочарование в том, что Swift не дает возможности ограничивать defaultSayHello
для разработчиков Protocol
, Технически это может назвать каждый. Иногда стоит поставить перед ним префикс _
указать, что посторонние не должны. Это основная проблема контроля доступа в протоколах, не имеющая ничего общего с этим конкретным вопросом; он появляется все время, когда вам нужны "вещи, которые мои разработчики могут использовать сами, но не должны вызываться случайным образом". Свифт не имеет решения для этого сегодня.
Вдохновленный ответом Роба Напира, вот что я сделал; старая добрая перегрузка по умолчанию:
protocol Protocol {
associatedtype AssociatedType
}
extension Protocol {
func sayHello(name: String = "World") {
print("Hello, \(name)!")
}
}
class Class<T>: Protocol {
typealias AssociatedType = T
func sayHello() {
self.sayHello("Stack Overflow")
}
}
Class<()>().sayHello() // Hello, Stack Overflow!
Class<()>().sayHello("you") // Hello, you!
Это соответствует моим потребностям, но не отвечает на вопрос. Так что я не удовлетворен на 100%.
Я считаю, что Rust делает это правильно, позволяя чертам / протоколам быть общими для обоих X<T>
и связанные типы.