我对Swift很新,但我对OO编程有一些经验。我已经开始尝试在Swift中使用参数化类,我在重载方法时遇到了一个奇怪的设计特性。如果我定义以下类:
class ParameterClassA {
}
class ParameterClassB: ParameterClassA {
}
class WorkingClassA<T: ParameterClassA> {
func someFunction(param: T) -> Void {
}
}
class WorkingClassB: WorkingClassA<ParameterClassB> {
override func someFunction(param: ParameterClassA) {
}
}
然后代码编译得很好。但是,正如您所注意到的,我已经重载了通常使用参数类型的函数,在我的示例中为ParameterClassB
,并为其指定了ParameterClassA
类型的参数。这应该怎么样?我知道Java中不允许这样做,我想知道如何解释类型参数。它可以是类型参数的类层次结构中的任何内容吗?
另请注意,如果我删除: ParameterClassA
中的类型参数约束WorkingClassA
,则问题完全相同。
如果我删除了override
关键字,那么我收到编译器错误,要求我添加它。
非常感谢任何解释!
答案 0 :(得分:2)
它与泛型(你称之为“参数化”)没有任何关系。它与一个函数类型如何在Swift中替代另一个函数类型有关。规则是函数类型相对于它们的参数类型是逆变。
为了更清楚地看到这一点,它将有助于丢弃所有误导性的通用内容和覆盖内容,而是直接集中于将一种函数类型替换为另一种函数类型的业务:
class A {}
class B:A {}
class C:B {}
func fA (x:A) {}
func fB (x:B) {}
func fC (x:C) {}
func f(_ fparam : B -> Void) {}
let result1 = f(fB) // ok
let result2 = f(fA) // ok!
// let result3 = f(fC) // not ok
我们希望将函数f
作为第一个参数传递给B -> Void
类型的函数,但是类型A -> Void
的函数是可接受的,其中A是B的超类
但C -> Void
类型的函数不可接受,其中C是B的子类。函数在其参数类型上是逆变的,而不是协变的。
答案 1 :(得分:2)
@matt is completely right关于为什么会这样做 - 这是因为方法输入是 contra 变体,而不是 co </ em>变体。
这意味着您只能使用另一个具有更宽(或相同)输入类型的函数覆盖给定函数 - 这意味着您可以用超类参数代替子类参数。这一开始可能看起来完全倒退 - 但如果你仔细考虑一下,这就完全有道理了。
我在您的情况下解释它的方式是使用稍微简化的代码版本:
class ParameterClassA {}
class ParameterClassB: ParameterClassA {}
class WorkingClassA {
func someFunction(param: ParameterClassB) {}
}
class WorkingClassB: WorkingClassA {
override func someFunction(param: ParameterClassA) {}
}
请注意,WorkingClassB
覆盖了someFunction
ParameterClassA
- ParameterClassB
的超类。
现在,假设您有一个WorkingClassA
的实例,然后在此实例上调用someFunction
,其实例为ParameterClassB
:
let workingInstanceA = WorkingClassA()
workingInstanceA.someFunction(ParameterClassB()) // expects ParameterClassB
到目前为止,没什么不寻常的。我们将ParameterClassB
实例传递给期望ParameterClassB
。
现在让我们假设您使用WorkingClassA
实例交换您的WorkingClassB
实例。这在OOP中是完全合法的 - 因为子类可以完成超类所能做的所有事情。
let workingInstanceB = WorkingClassB()
workingInstanceB.someFunction(ParameterClassB()) // expects ParameterClassA
那么现在发生了什么?我们仍然将ParameterClassB
实例传递给该函数。但是,现在该函数需要一个ParameterClassA
实例。将子类传递给期望超类的参数在OOP中是合法的(这是 co </ em>方差),因为子类可以执行超类可以执行的所有操作 - 因此这不会破坏任何内容。
因为函数签名只能在覆盖它时变得更广泛(或保持不变),所以它确保您始终可以将函数定义的原始参数类型传递给它,因为重写版本中的任何超类参数都可以接受它
如果你想到反过来一秒,你就会明白为什么它不可行。由于该函数在被覆盖时会获得更多限制性,因此它无法接受超类最初可以接受的参数 - 因此它无法工作。