Быстрые расширения протоколов
Я экспериментирую с расширениями протокола Swift и обнаружил, что это довольно запутанное поведение. Не могли бы вы помочь мне получить желаемый результат?
Смотрите комментарии к последним 4 строкам кода. (Вы можете скопировать и вставить его на игровую площадку Xcode7, если хотите). Спасибо!!
//: Playground - noun: a place where people can play
import UIKit
protocol Color { }
extension Color { var color : String { return "Default color" } }
protocol RedColor: Color { }
extension RedColor { var color : String { return "Red color" } }
protocol PrintColor {
func getColor() -> String
}
extension PrintColor where Self: Color {
func getColor() -> String {
return color
}
}
class A: Color, PrintColor { }
class B: A, RedColor { }
let colorA = A().color // is "Default color" - OK
let colorB = B().color // is "Red color" - OK
let a = A().getColor() // is "Default color" - OK
let b = B().getColor() // is "Default color" BUT I want it to be "Red color"
Ответы
Ответ 1
Короткий ответ заключается в том, что расширения протокола не выполняют классовый полиморфизм. Это имеет определенный смысл, поскольку протокол может быть принят структурой или перечислением, и потому, что мы не хотим, чтобы простое принятие протокола вводило динамическую отправку там, где это не нужно.
Таким образом, в getColor()
переменная экземпляра color
(которая может быть более точно записана как self.color
) не означает, что вы думаете, что она делает, потому что вы думаете, что класс-полиморфно, а протокол не, Итак, это работает:
let colorB = B().color // is "Red color" - OK
... потому что вы просите класс разрешить color
, но это не делает то, что вы ожидаете:
let b = B().getColor() // is "Default color" BUT I want it to be "Red color"
... потому что метод getColor
полностью определен в расширении протокола. Вы можете исправить проблему, переопределив getColor
в B:
class B: A, RedColor {
func getColor() -> String {
return self.color
}
}
Теперь вызывается класс getColor
, и он имеет полиморфную идею того, что self
.
Ответ 2
Мне удалось заставить его работать, указав color
на color
и переключив список реализации для B. Не очень хорошо, если B
должен быть A
, хотя.
protocol Color {
var color : String { get }
}
protocol RedColor: Color {
}
extension Color {
var color : String {
get {return "Default color"}
}
}
extension RedColor {
var color : String {
get {return "Red color"}
}
}
protocol PrintColor {
func getColor() -> String
}
extension PrintColor where Self: Color {
func getColor() -> String {
return color
}
}
class A : Color, PrintColor {
}
class B : RedColor, PrintColor {
}
let a = A().getColor() // "Default color"
let b = B().getColor() // "Red color"
Ответ 3
Здесь есть две очень разные проблемы: динамическое поведение протоколов и разрешение реализаций протокола по умолчанию.
-
В динамическом фронте мы можем проиллюстрировать проблему простым примером:
protocol Color { }
extension Color {
var color: String { return "Default color" }
}
class BlueBerry: Color {
var color: String { return "Blue color" }
}
let berry = BlueBerry()
print("\(berry.color)") // prints "Blue color", as expected
let colorfulThing: Color = BlueBerry()
print("\(colorfulThing.color)") // prints "Default color"!
Как вы указываете в ваш ответ, вы можете получить динамическое поведение, если вы определяете color
как часть исходного протокола color
(т.е. тем самым инструктируя компилятор, чтобы разумно ожидать соответствия классов для реализации этого метода и использовать только реализацию протокола, если ни один не найден):
protocol Color {
var color: String { get }
}
...
let colorfulThing: Color = BlueBerry()
print("\(colorfulThing.color)") // now prints "Blue color", as expected
-
Теперь, в ваш ответ, вы спрашиваете, почему это немного распадается, когда B
является подклассом A
.
Я думаю, что это помогает помнить, что реализации метода в расширениях протокола являются реализациями по умолчанию, то есть реализациями, которые будут использоваться, если соответствующий класс не реализует ее сам. Источником путаницы в вашем случае является тот факт, что B
соответствует RedColor
, который имеет реализацию по умолчанию для color
, но B
также является подклассом A
, который соответствует color
, который имеет различную реализацию по умолчанию color
.
Итак, мы можем поспорить о том, что Swift справляется с этой ситуацией (лично я предпочел бы увидеть предупреждение об этой неотъемлемо неоднозначной ситуации), но корень проблемы, на мой взгляд, состоит в том, что существуют две разные иерархии ( OOP-иерархии объектов подклассов и иерархии протокола протокола POP наследования протокола), и это приводит к двум конкурирующим реализациям по умолчанию.
Я знаю, что это старый вопрос, поэтому вы, вероятно, давно перешли к другим вещам, и это нормально. Но если вы все еще боретесь за правильный способ реорганизации этого кода, поделитесь немного информацией о том, что представляет собой эта иерархия классов и что на самом деле представляет наследование этого протокола, и мы могли бы предложить более конкретный совет. Это один из тех случаев, когда абстрактные примеры еще больше путают проблему. Посмотрим, какие типы/протоколы действительно есть. (Если у вас есть рабочий код, http://codereview.stackexchange.com может стать лучшим местом.)
Ответ 4
Я столкнулся с этой проблемой, пытаясь реализовать "дополнительный" метод через протокол. Его можно заставить работать, в структурах, в классах, которые не наследуются, а также в классах, которые наследуются от базы, которая реализует метод, отличный от протокола, который можно переопределить. Единственный случай, который не работает, - это класс, который наследует базу, декларирующую соответствие, но не предоставляет свою собственную реализацию "не по умолчанию" - в этом случае расширение протокола по умолчанию "испечено" базовому классу, и его нельзя переопределить или переопределить.
Простой пример:
typealias MyFunction = () -> ()
protocol OptionalMethod {
func optionalMethod() -> MyFunction?
func executeOptionalMethod()
}
extension OptionalMethod {
func optionalMethod() -> MyFunction? { return nil }
func executeOptionalMethod() {
if let myFunc = self.optionalMethod() {
myFunc()
} else {
print("Type \(self) has not implemented `optionalMethod`")
}
}
}
class A: OptionalMethod {
}
class B: A {
func optionalMethod() -> MyFunction? {
return { print("Hello optional method") }
}
}
struct C: OptionalMethod {
func optionalMethod() -> MyFunction? {
return { print("Hello optionalMethod") }
}
}
class D: OptionalMethod {
func optionalMethod() -> MyFunction? {
return { print("Hello optionalMethod") }
}
}
class E: D {
override func optionalMethod() -> MyFunction? {
return { print("Hello DIFFERENT optionalMethod") }
}
}
/* Attempt to get B to declare its own conformance gives:
// error: redundant conformance of 'B2' to protocol 'OptionalMethod'
class B2: A, OptionalMethod {
func optionalMethod() -> MyFunction? {
return { print("Hello optional method") }
}
}
*/
class A2: OptionalMethod {
func optionalMethod() -> MyFunction? {
return nil
}
}
class B2: A2 {
override func optionalMethod() -> MyFunction? {
return { print("Hello optionalMethod") }
}
}
let a = A() // Class A doesn't implement & therefore defaults to protocol extension implementation
a.executeOptionalMethod() // Type __lldb_expr_201.A has not implemented `optionalMethod`
let b = B() // Class B implements its own, but "inherits" implementation from superclass A
b.executeOptionalMethod() // Type __lldb_expr_205.B has not implemented `optionalMethod`
let c = C() // Struct C implements its own, and works
c.executeOptionalMethod() // Hello optionalMethod
let d = D() // Class D implements its own, inherits from nothing, and works
d.executeOptionalMethod() // Hello optionalMethod
let e = E() // Class E inherits from D, but overrides, and works
e.executeOptionalMethod() // Hello DIFFERENT optionalMethod
let a2 = A2() // Class A2 implements the method, but returns nil, (equivalent to A)
a2.executeOptionalMethod() // Type __lldb_expr_334.A2 has not implemented `optionalMethod`
let b2 = B2() // Class B2 overrides A2 "nil" implementation, and so works
b2.executeOptionalMethod() // Hello optionalMethod
Ответ 5
Примечание. Предложенное решение "Определение color
как части исходного Color
протокола" не решает проблему, если задействовано наследование, например, RedBerry
наследуется от BlueBerry
который соответствует протоколу Color
.
protocol Color {
var color: String { get }
}
extension Color {
var color: String { return "Default color" }
}
class BlueBerry: Color {
// var color: String { return "Blue color" }
}
class RedBerry: BlueBerry {
var color: String { return "Red color" }
}
let berry = RedBerry()
print(berry.color) // Red color
let colorfulThing: Color = RedBerry()
print(colorfulThing.color) // Actual: Default color, Expected: Red color