Swift:参数化类中的方法重写

时间:2016-05-06 18:41:47

标签: swift generics inheritance overloading

我对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关键字,那么我收到编译器错误,要求我添加它。

非常感谢任何解释!

2 个答案:

答案 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>方差),因为子类可以执行超类可以执行的所有操作 - 因此这不会破坏任何内容。

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

如果你想到反过来一秒,你就会明白为什么它不可行。由于该函数在被覆盖时会获得更多限制性,因此它无法接受超类最初可以接受的参数 - 因此它无法工作。