【问题标题】:Kotlin Interface method abstractionKotlin 接口方法抽象
【发布时间】:2021-09-07 11:13:11
【问题描述】:

我正在探索 Substitution 主体,根据我对主体的理解,任何超类型的子类型都应该可以传递给函数/类。在我正在编写的一段新代码中使用这个想法,我想为这样的过滤器实现一个抽象接口

interface Filter {
    fun filter(): Boolean
}

然后我会想象这会为所有继承此接口的类创建契约,它们必须实现函数过滤器并返回布尔输出。现在我对此的解释是不需要指定输入。我希望这样,因为我想要一个过滤器接口来保证过滤器方法的实现,并保证返回类型为布尔值。这个概念甚至存在于 Kotlin 中吗?然后我希望像这样实现这个接口

class LocationFilter {
    companion object : Filter {
        override fun filter(coord1: Coordinate, coord2: Coordinate): Boolean {
            TODO("Some business logic here")
        }
    }
}

但实际上这行不通。我可以从界面中删除删除过滤器方法,但这只会破坏整个练习的重点。我尝试过使用varargs,但这并不能解决问题,因为每个覆盖都必须实现varargs,这没有帮助。我知道这似乎是多余的,但是有没有可能拥有我要求的抽象类型?还是我错过了一个接口?

【问题讨论】:

  • 我想我的方法会让界面的订阅者失去知道输入类型的能力。我想那会很糟糕......
  • 您能否举例说明如何通过Filter 接口调用filter 方法?你会意识到这是不可能的——调用者不知道具体的参数类型和实现需要的参数数量。

标签: kotlin interface


【解决方案1】:

让我们考虑一下。抽象的要点是,无论实现是什么,我们都可以使用Filter。我们不需要知道实现,我们只需要知道接口。但是,如果我们不知道必须提供哪些数据来过滤,我们怎么能使用Filter?我们需要直接使用LocationFilter,这也违背了创建界面的意义。

您的问题实际上与 Kotlin 无关,而是与一般的 OOP 相关。在大多数语言中,它由泛型/模板/参数化类型解决。这意味着接口/类由另一种类型参数化。你可以像这样在 Kotlin 中使用它:

interface Filter<in T> {
    fun filter(value: T): Boolean
}

object LocationFilter : Filter<Coordinate> {
    override fun filter(value: Coordinate): Boolean {
        TODO()
    }
}

fun acquireCoordinateFilter(): Filter<Coordinate> = LocationFilter

fun main() {
    val coord: Coordinate = TODO()
    val filter: Filter<Coordinate> = acquireCoordinateFilter()

    val result = filter.filter(coord)
}

Filter 是参数化的,这意味着我们可以有一个过滤器来过滤字符串(类型为:Filter&lt;String&gt;),过滤整数(Filter&lt;Int&gt;)或过滤坐标(Filter&lt;Coordinate&gt;)。然后我们不能使用例如Filter&lt;String&gt; 过滤整数。

注意main()中的代码并没有直接使用LocationFilter,它只知道如何获取Filter&lt;Coordinate&gt;,但具体实现是从中抽象出来的。

另请注意,Java stdlib 中已经有一个非常相似的接口。它被称为Predicate

【讨论】:

    【解决方案2】:

    我对此的解释是不需要指定输入。

    你从哪里得到这样的解释?

    通过查看该方法的调用方式,您可以看到它不可能是正确的。您应该能够编写适用于Filter任何 实例的代码——并且只有在接口中指定参数的数量和类型时才会发生这种情况。使用您的示例:

    val f: Filter = someMethodReturningAFilterInstance()
    val result = f.filter(coord1, coord2)
    

    只有在 all 实现使用两个 Coordinate 参数时才能工作。如果有些人使用了一个 String 参数,而有些人根本不使用,那你如何安全地调用它?

    您可以使用一些解决方法。

    如果每个实现都采用相同的数量个参数,那么您可以使用类型参数使接口成为通用接口,例如:

    interface Filter<T1, T2> {
        fun filter(t1: T1, t2: T2): Boolean
    }
    

    然后由实现来指定需要哪些类型。但是,调用代码要么需要知道特定实现的类型,要么需要本身是泛型的,或者接口需要提供具有in 变化的类型边界。

    或者,如果您需要可变数量的参数,您可以将它们捆绑到一个对象中并传递它。但是,您可能需要该类型的接口,以便处理不同数量和类型的参数,和/或使该类型成为Filter 上的类型参数——所有这些都很难闻。

    最终,我怀疑您需要考虑如何使用您的接口,尤其是如何调用它的方法。如果您只在调用者知道实现类型时才调用它,那么尝试在接口中指定该方法可能没有意义(并且可能根本没有接口)。或者,如果您想要处理 Filter 实例 不知道它们的具体类型,那么看看您将如何进行这些调用。

    【讨论】:

      【解决方案3】:

      这都是错的!

      首先,OOP 是一个 声明性 概念,但在您的示例中,Filter 类型只是一个包装在对象中的过程。这是完全错误的。

      为什么需要这种类型Filter?我假设您需要获得一个集合过滤,那么为什么不创建一个接受现有集合并表示它已过滤的新对象。

      class Filtered<T>(private val origin: Iterable<T>) : Iterable<T> {
          
          override fun iterator(): Iterator<T> {
              TODO("Filter the original iterable and return it")
          }
      }
      

      然后在你的代码中,任何你可以传递Iterable 并且你希望它被过滤的地方,你只需像这样用类Filtered 包装这个原始的可迭代对象(任何列表、数组或集合)

      acceptCollection(Filtered(listOf(1, 2, 3, 4)))
      

      您还可以将第二个参数传递给Filtered 并调用它,例如,谓词,它是一个接受可迭代元素并返回布尔值的 lambda。

      class Filtered<T>(private val origin: Iterable<T>, private val predicate: (T) -> Boolean) : Iterable<T> {
          
          override fun iterator(): Iterator<T> {
              TODO("Filter the original iterable and return it")
          }
      }
      

      然后像这样使用它:

      val oddOnly = Filtered(
          listOf(1, 2, 3, 4),
          { it % 2 == 1 }
      )
      
      
      

      【讨论】:

        猜你喜欢
        • 2020-02-04
        • 2013-07-21
        • 2013-01-19
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-03
        • 2011-04-11
        相关资源
        最近更新 更多