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"
Author: VojtaStavik, 2015-07-15

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.

 41
Author: matt,
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"
 4
Author: Ian Warburton,
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.

  1. 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łu Color (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
    
  2. 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ę dla color, ale B jest również podklasą A, która jest zgodna z Color, 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.)

 3
Author: Rob,
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
 0
Author: Grimxn,
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
 0
Author: Grand M,
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