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

Swift: Method overriding in parameterized class

我是 Swift 的新手,但我有一些面向对象编程的经验。我已经开始尝试在 Swift 中使用参数化 classes 并且在重载方法时遇到了一个奇怪的设计特征。如果我定义以下 classes:

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

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

如果删除 override 关键字,则会出现编译器错误,要求我添加它。

非常感谢任何解释!

它与泛型(您所说的 "parameterized")完全无关。它与 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

我们希望将 B -> Void 类型的函数作为第一个参数传递给函数 f,但可以接受 A -> Void 类型的函数,其中 A 是超类B.

但是C -> Void类型的函数可接受,其中C是B的子类。函数在它们的参数类型上是逆变的,而不是协变的。

关于为什么这有效——这是因为方法输入是 contra 变体,而不是 co变体。

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

在你的情况下,我会用你的代码的一个稍微精简的版本来解释它:

class ParameterClassA {}
class ParameterClassB: ParameterClassA {}

class WorkingClassA {
    func someFunction(param: ParameterClassB) {}
}

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

注意这里 WorkingClassBParameterClassA 覆盖 someFunctionParameterClassB 的超类。

现在,假设您有一个 WorkingClassA 的实例,然后使用 ParameterClassB 的实例调用此实例的 someFunction:

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 方差),因为子类可以做超类可以做的所有事情——因此这不会破坏任何东西.

因为函数签名只能在您覆盖它时变得更广泛(或保持不变),所以它确保您始终可以将函数定义的原始参数类型传递给它,因为覆盖版本中的任何超类参数都可以接受它。

如果您稍微考虑一下反向操作,您就会明白为什么它不可能工作。由于该函数在被覆盖时会变得更加限制,它将无法接受超类最初可以接受的参数——因此它无法工作。