如何强制一个类具有一个伴随对象?

时间:2019-12-20 11:24:03

标签: kotlin

想要是什么:

interface base {
  abstract static fun foo()
}

class impl : base { 
  override static fun foo()
}

通常,Kotlin使用伴随对象而不是静态函数来解决问题。但是接口无法定义对具有功能的伴随对象的要求。那我该怎么做呢?使用此代码的代码看起来像

fun <T : base> bar() {
  T.foo()
}

还有其他方法可以得到这种行为吗?也就是说,我可以执行T的派生函数而无需知道具体类型,并且不假设派生具有默认构造函数吗?

修改

通过使用可以在要使用的类的伴随对象上设置的类型的值参数,我能够做到这一点。我想要将此技术用于什么的说明性示例。

import kotlin.reflect.full.*

interface DynamicBuilder {
    fun build(sides: Int): Shape?
}

interface Shape {
    companion object : DynamicBuilder {
        override fun build(sides: Int) = null
    }
}

abstract class Shape2D : Shape {
    companion object : DynamicBuilder {
        override fun build(sides: Int) = if(sides > 0) Square() else Circle()
    }
}

abstract class Shape3D : Shape {
    companion object : DynamicBuilder {
        override fun build(sides: Int) = if(sides > 0) Cube() else Sphere()
    }
}

class Square : Shape2D()
class Circle : Shape2D()
class Sphere : Shape3D()
class Cube : Shape3D()

fun Build(sides: Int, builder: DynamicBuilder): Shape? {
    return builder.build(sides)
}

inline fun <reified T : Shape> Build(sides: Int): Shape? {
    return Build(sides, T::class.companionObjectInstance as DynamicBuilder)
}

fun main() {
    println(Build(0, Shape2D))
    println(Build(4, Shape2D))

    println(Build<Shape3D>(0))
    println(Build<Shape3D>(6))
}

目标是我可以创建一个Shape的新类,并具有与其如何构建包含在该文件中的具体对象有关的所有逻辑,而不是具有某些整体式共享switch语句。

3 个答案:

答案 0 :(得分:2)

接口可以定义具有功能的 some 对象的要求,即使您不能强迫它成为伴生对象,也可以建议它。

interface BaseCompanion {
    fun foo(): Unit
}

interface Base {
    companion object : BaseCompanion {
        fun foo() { println("in Base") }
    }

    fun companion(): BaseCompanion = Base
}

interface Derived : Base {
    companion object : BaseCompanion {
        fun foo() { println("in Derived") }
    }

    override fun companion() = Derived
}

// value parameter, not type parameter
fun bar(companion: BaseCompanion) {
    companion.foo()
}

bar(Base)
bar(Derived)

在这种情况下,实际上并未使用companion()函数,它是用于您要从Base实例访问随播广告的情况:

fun baz(x: Base) {
    x.companion().foo()
}

另一个(不安全的)选项是使用反射来定义companion()

fun companion() = this::class.companionObjectInstance as BaseCompanion

加:无需在Derived中显式覆盖它;缺点:1.如果忘记创建同伴或扩展BaseCompanion,则会在运行时崩溃; 2.比非反射定义要慢。

答案 1 :(得分:0)

TL; TR

  • 如何强制一个类具有伴随对象?
  • 你不能。

科特林没有静态方法。即使有它们,它们也不会被覆盖,因为它们不是Java语言。伴随对象也是如此。最终Kotlin代码被编译为Java字节码,因此Kotlin也无法实现Java中不可能实现的功能。

编辑:

看看编译器对此有什么看法很有趣。请考虑以下代码段:

open class Base {
    companion object {
        fun test() {}
    }
}

inline fun <reified T : Base> staticCall() {
    T.test() // <-- ERROR
}

错误消息:

  

类型参数“ T”不能具有或继承伴随对象,因此它不能位于点的左侧

答案 2 :(得分:0)

根据您更新的问题,通常可以使用工厂模式实现所需的目标。另外,您也可以使用依赖注入。有很多选项不使用反射。

为什么不使用反射?

herehere的几个原因,如果您使用Google可以找到更多的原因。通常,反射是为特定目的而创建的,以发现编译时未知的类的功能。请勿将其用于此目的,因为您的实现要求您知道该类,以便将其作为经过修饰的泛型参数进行传递。如果确实需要发现在编译时不知道的类,则可以使用依赖注入。

您的版本最简单的解决方案是工厂模式:

interface Shape
class Square : Shape
class Circle : Shape
class Sphere : Shape
class Cube : Shape

object ShapeFactory {
    fun build2DShape(sides: Int): Shape {
        if(sides > 0) Square() else Circle()
    }

    fun build3DShape(sides: Int): Shape {
        if(sides > 0) Cube() else Sphere()
    }
}

fun main() {
    println(ShapeFactory.build2DShape(0))
    println(ShapeFactory.build3DShape(0))
}

简而言之,Build<Shape3D>(0)ShapeFactory.build3DShape(0)代替。呼叫者仍然必须知道3DShapes及其位置。唯一改变的是您不需要反射。

这要求调用函数的人员知道2D和3D形状的存在。与实施反射相同。通过这种方式,您可以拥有所有逻辑以及如何在与形状相同的文件中创建形状。如果愿意,您甚至可以在工厂的形状对象中调用某些函数。您的工厂知道这些子类的存在。但是,由于您可以将工厂和子类放在同一个文件中,因此不会将逻辑拆分到其他地方。

如果要将决定是2D形状还是3D形状委托给子类,则可以执行以下操作:

interface Shape
class Square : Shape
class Circle : Shape
class Sphere : Shape
class Cube : Shape

object ShapeFactory {
    fun build2DShape(sides: Int): Shape {
        return if(sides > 0) Square() else Circle()
    }

    fun build3DShape(sides: Int): Shape {
        return if(sides > 0) Cube() else Sphere()
    }
}

fun getBuilder(dimensions: Int): (sides: Int) -> Shape {
    if (dimensions == 2)
        return ShapeFactory::build2DShape
    else 
        return ShapeFactory::build3DShape
}

fun main() {
    print (getBuilder(2)(3))
}