Reputation: 629
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?
Upvotes: 0
Views: 98
Reputation: 63274
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 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:
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.Car
, and change the parameter to Vehicle
. Doing so preserves your classes' ability to work with `Vehicles.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.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.Upvotes: 4
Reputation: 200
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)
}
}
Upvotes: 2