Swift:参数化类中的方法覆盖



我对 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 中是不允许的,我想知道类型参数是如何解释的。它可以是类型参数的类层次结构中的任何内容吗?

另请注意,如果我删除 WorkingClassA 中的类型参数约束: ParameterClassA,问题完全相同。

如果我删除 override 关键字,则会收到一个编译器错误,要求我添加它。

非常感谢任何解释!

它与泛型(你称之为"参数化"(完全无关。它与 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 的子类。函数在其参数类型上是逆变的,而不是协变的。

@matt关于

为什么这样做是完全正确的 - 这是因为方法输入是相反变体,而不是co变体。

这意味着您只能用另一个具有更广泛(或相同(输入类型的函数覆盖给定函数 - 这意味着您可以替换超类参数来代替子类参数。乍一看,这似乎完全倒退了——但如果你仔细想想,这是完全有道理的。

我在您的情况下解释它的方式是使用略微精简的代码版本:

class ParameterClassA {}
class ParameterClassB: ParameterClassA {}
class WorkingClassA {
    func someFunction(param: ParameterClassB) {}
}
class WorkingClassB: WorkingClassA {
    override func someFunction(param: ParameterClassA) {}
}

请注意,WorkingClassBParameterClassA 覆盖someFunctionParameterClassB的超类。

现在,假设您有一个 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 中是合法的(这是方差(,因为子类可以做超类可以做的所有事情——因此这不会破坏任何东西。

由于函数签名只会在重写时变得更宽(或保持不变(,因此它确保始终可以向其传递函数定义的原始参数类型,因为重写版本中的任何超类参数都可以接受它。

如果你考虑一下相反的情况,你就会明白为什么它不可能奏效。由于该函数在被覆盖时会变得更加严格,因此它将无法接受超类最初可以接受的参数 - 因此它无法工作。

最新更新