Rozszerzenia protokołu Swift
Eksperymentuję z rozszerzeniami protokołu Swift i znalazłem to dość mylące zachowanie. Czy możesz mi pomóc, jak uzyskać pożądany rezultat?
Zobacz komentarze do ostatnich 4 linijek kodu. (Możesz skopiować wklej do xcode7 plac zabaw, jeśli chcesz). Dziękuję!!
//: 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"
5 answers
Krótka odpowiedź jest taka, że rozszerzenia protokołu nie powodują polimorfizmu klas. Ma to pewien sens, ponieważ protokół może być przyjęty przez strukturę lub enum i ponieważ nie chcielibyśmy, aby samo przyjęcie protokołu wprowadziło dynamiczną wysyłkę tam, gdzie nie jest to konieczne.
Tak więc, w getColor()
, zmienna instancji color
(która może być dokładniej zapisana jako self.color
) nie oznacza tego, co myślisz, że robi, ponieważ myślisz o klasie-polimorficznie, a protokół nie jest. Więc to działa:
let colorB = B().color // is "Red color" - OK
...ponieważ prosisz klasę o rozwiązanie color
, ale to nie robi tego, czego oczekujesz:
let b = B().getColor() // is "Default color" BUT I want it to be "Red color"
...ponieważ metoda getColor
jest zdefiniowana w całości w rozszerzeniu protokołu. Problem można rozwiązać, zmieniając definicję getColor
W B:
class B: A, RedColor {
func getColor() -> String {
return self.color
}
}
Teraz nazywa się klasę getColor
i ma polimorficzną ideę tego, czym jest self
.
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-07-15 14:02:14
Udało mi się go uruchomić definiując color
na Color
i zmieniając listę implementacji na B. nie bardzo dobrze, jeśli B
musi być 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"
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-07-12 19:03:52
W grę wchodzą dwie bardzo różne kwestie: dynamiczne zachowanie protokołów i rozwiązywanie "domyślnych" implementacji protokołów.
-
Na dynamicznym froncie możemy zilustrować problem prostym przykładem:
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"!
Jak wskazujesz w twojej odpowiedzi , możesz uzyskać dynamiczne zachowanie, jeśli zdefiniujesz
color
jako część oryginalnego protokołuColor
(tj. nakazując kompilatorowi rozsądne oczekiwanie zgodnych klas zaimplementuj tę metodę i używaj implementacji protokołu tylko wtedy, gdy żadna nie zostanie znaleziona):protocol Color { var color: String { get } } ... let colorfulThing: Color = BlueBerry() print("\(colorfulThing.color)") // now prints "Blue color", as expected
-
Teraz, w twojej odpowiedzi , pytasz, dlaczego to się trochę rozpada, gdy
B
jest podklasąA
.Myślę, że pomaga pamiętać, że implementacje metod w rozszerzeniach protokołów są" domyślnymi " implementacjami, tzn. implementacjami, które mają być używane, jeśli Klasa zgodna sama tego nie implementuje. Źródło zamieszania w Twoim przypadku pochodzi z fakt, że
B
jest zgodna z {[7] } , która ma domyślną implementację dlacolor
, aleB
jest również podklasąA
, która jest zgodna zColor
, która ma inną domyślną implementacjęcolor
.Możemy więc spierać się o to, jak Swift radzi sobie z tą sytuacją( osobiście wolałbym widzieć ostrzeżenie o tej z natury niejednoznacznej sytuacji), ale głównym problemem, moim zdaniem, jest to, że istnieją dwie różne hierarchie (hierarchia obiektów OOP podklas i POP). protocol hierarchia of protocol inheritance), co skutkuje dwoma konkurującymi ze sobą" domyślnymi " implementacjami.
Wiem, że to stare pytanie, więc pewnie już dawno przeszedłeś do innych rzeczy, co jest w porządku. Ale jeśli nadal zmagasz się z właściwym sposobem refaktoryzacji tego kodu, podziel się trochę tym, co ta hierarchia klas i co faktycznie reprezentuje dziedziczenie protokołu, a my możemy zaoferować bardziej konkretne porady. Jest to jeden z tych przypadków, w których abstrakcyjne przykłady tylko jeszcze bardziej mylą tę kwestię. Zobaczmy, jakie są typy/protokoły. (Jeśli masz działający kod, http://codereview.stackexchange.com Może być lepsze miejsce.)
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:34:34
Natknąłem się na ten problem próbując zaimplementować "opcjonalną" metodę poprzez protokół. Może działać w strukturach, klasach, które nie dziedziczą, a także w klasach, które dziedziczą z bazy, która implementuje domyślną metodę, która może być nadpisana. Jedynym przypadkiem, który nie działa, jest klasa, która dziedziczy z bazy, która deklaruje Zgodność, Ale nie dostarcza własnej "niestandardowej" implementacji - w takim przypadku domyślnym rozszerzeniem protokołu jest "baked-in" do klasy bazowej i nie może być nadpisany ani ponownie zdefiniowany.
Prosty przykład:
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's "nil" implementation, and so works
b2.executeOptionalMethod() // Hello optionalMethod
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-10-04 17:50:41
Uwaga: proponowane rozwiązanie "Definiowanie {[1] } jako części oryginalnego protokołu Color
" nie rozwiązuje problemu, gdy mamy do czynienia z dziedziczeniem np.
RedBerry
dziedziczy z BlueBerry
, który jest zgodny z protokołem 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-04-11 14:19:12