【问题标题】:Swift: Method overriding in parameterized classSwift:参数化类中的方法覆盖
【发布时间】:2016-05-06 18:41:46
【问题描述】:

我对 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 generics inheritance overloading


    【解决方案1】:

    它与泛型(所谓的“参数化”)毫无关系。它与 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 -&gt; Void 类型的函数作为其第一个参数传递给函数f,但也可以使用A -&gt; Void 类型的函数,其中A 是B 的超类。

    但是C -&gt; Void 类型的函数是可接受的,其中 C 是 B 的子类。函数的参数类型是逆变的,而不是协变的。

    【讨论】:

    【解决方案2】:

    @matt is completely right 关于为什么会这样 - 这是因为方法输入是相反变体,而不是co变体。

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

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

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

    请注意,WorkingClassBParameterClassAParameterClassB 的超类)覆盖了 someFunction

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

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

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

    【讨论】:

      猜你喜欢
      • 2012-01-08
      • 1970-01-01
      • 2017-05-11
      • 1970-01-01
      • 1970-01-01
      • 2014-07-29
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多