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) {}
}
注意这里 WorkingClassB
用 ParameterClassA
覆盖 someFunction
– ParameterClassB
的超类。
现在,假设您有一个 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 方差),因为子类可以做超类可以做的所有事情——因此这不会破坏任何东西.
因为函数签名只能在您覆盖它时变得更广泛(或保持不变),所以它确保您始终可以将函数定义的原始参数类型传递给它,因为覆盖版本中的任何超类参数都可以接受它。
如果您稍微考虑一下反向操作,您就会明白为什么它不可能工作。由于该函数在被覆盖时会变得更加限制,它将无法接受超类最初可以接受的参数——因此它无法工作。
我是 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的子类。函数在它们的参数类型上是逆变的,而不是协变的。
这意味着您只能使用具有 更广泛 (或相同)输入类型的另一个函数覆盖给定函数 – 这意味着您可以在超类参数中替换子类参数的位置。乍一看,这似乎完全是倒退的——但如果你仔细想想,这是完全有道理的。
在你的情况下,我会用你的代码的一个稍微精简的版本来解释它:
class ParameterClassA {}
class ParameterClassB: ParameterClassA {}
class WorkingClassA {
func someFunction(param: ParameterClassB) {}
}
class WorkingClassB: WorkingClassA {
override func someFunction(param: ParameterClassA) {}
}
注意这里 WorkingClassB
用 ParameterClassA
覆盖 someFunction
– ParameterClassB
的超类。
现在,假设您有一个 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 方差),因为子类可以做超类可以做的所有事情——因此这不会破坏任何东西.
因为函数签名只能在您覆盖它时变得更广泛(或保持不变),所以它确保您始终可以将函数定义的原始参数类型传递给它,因为覆盖版本中的任何超类参数都可以接受它。
如果您稍微考虑一下反向操作,您就会明白为什么它不可能工作。由于该函数在被覆盖时会变得更加限制,它将无法接受超类最初可以接受的参数——因此它无法工作。