简体   繁体   中英

Swift, classes based on extended protocol don't conform to original protocol

These protocols are giving me nightmares.

I am trying to implement a couple protocols and classes conforming to them, such that I can have default implementations, but customized implementations are available by extending the protocols/classes. So far, this is what I have:

protocol ProtA {
    var aProperty: String { get set }
    var anotherProperty:String { get set }

    func aFunc (anArgument: String) -> String
}

protocol ProtB: ProtA {
    var aThirdProperty: String { get set }
}

protocol ProtC {
    func doSomething(parameter: Int, with anotherParameter: ProtA)
}

class ClassA: ProtA {
    var aProperty: String = "Hello, I'm a String."
    var anotherProperty: String = "I'm not the same String."

    func aFunc (anArgument: String) -> String {
        return anArgument
    }
}

class ClassB: ProtB {

    var aProperty: String = "Hello, I'm a String."
    var anotherProperty: String = "I'm not the same String."
    var aThirdProperty: String = "I'm yet another String!"

    func aFunc (anArgument: String) -> String {
        return anArgument
    }
}



class ClassC: ProtC {

func doSomething(parameter: Int, with anotherParameter: ProtA) {
    print (anotherParameter.aProperty) // Works fine.

}

}

Then, if I do

class ClassC: ProtC {

    func doSomething(parameter: Int, with anotherParameter: ProtA) {
        print (anotherParameter.aProperty) // Works fine.

    }

}

But, if I do

class ClassD: ProtC {

    func doSomething(parameter: Int, with anotherParameter: ProtA) {

        print (anotherParameter.aThirdProperty) // Value of type 'ProtA' has no member 'aThirdProperty'

    }

}

and, if instead I do

class ClassE: ProtC {

    func doSomething(parameter: Int, with anotherParameter: ProtB) {

        print (anotherParameter.aThirdProperty) // Type 'ClassE' does not conform to protocol 'ProtC'

    }

}

What am I doing wrong?

The problem

When inheriting from a type, you cannot narrow down the types of the parameters used in overridden functions. This is what you've done by changing the parameter from type ProtA (a more general type), to ProtB (a more specific type).

This is a consequence of the Liskov substitution principle . Simply put, a subclass must be able to do ( at minimum ) everything that the superclass can do.

ProtC establishes that all conforming types have a function func doSomething(parameter: Int, with anotherParameter: ProtA) , with type (Int, ProtA) -> Void) .

Your modified function in ClassE has type (Int, ProtB) -> Void . However, this function can no longer act as a substitute for the one it overrides.

Suppose that it was possible to do what you tried. Watch what would happen:

let instanceConformingToProtA: ProtA = ClassA()

let instanceConformingToProtC: ProtC = ClassE()

// This would have to be possible:
instanceConformingToProtC(parameter: 0, amotherParameter: instanceConformingToProtA)

But, ClassE() can't take instanceConformingToProtA as a valid argument to its second parameter, because it's a ProtA , not the required ProtB .

The solution

The solution to this problem is entirely dependant on what you're trying to achieve. I would need further information before being able to proceed.

As a rule of thumb, when overriding inherited members:

  • Parameter types must be the same, or more general.
    • Eg you can't override a function with a parameter of type Car , and change the parameter type to RaceCar . Doing so breaks your classes ability to work with RaceCar s, which it must be able to do by the LSP.
    • Eg you can override a function with a parameter of type Car , and change the parameter to Vehicle . Doing so preserves your classes' ability to work with `Vehicles.
  • Return types must be the same, or more specific.
    • Eg you can't override a function with a return type of Car with a function that returns Vehicle . Doing so would mean the returned value is "less powerful" than the super class guarantees it should be.
    • Eg you can override a function with a return type of Car with a function that returns RaceCar . Doing so would mean the returned value is "more powerful", and it does at least as much as what the super class guarentees.

There's nothing wrong. You should just make sure that the declarations are semantically consistent. You should either create ProtD declaring the method with a ProtB parameter OR unwrapping the gotten ParamA parameter to use it as ProtB.

func doSomething(parameter: Int, with anotherParameter: ProtB) {
     if let a = anotherParameter as? ProtA {
          print (a.aThirdProperty)
     }

}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM