首页 > 解决方案 > 实现在对象注册表中注册自己的模式

问题描述

我有几个实现抽象类“产品”的类。为了遵循开闭原则,我希望实现类以一种将自己注册在ProductRegistry单例对象中的方式。添加新Product类时,编写它们或将它们放在类路径中就足够了,而不必触及ProductRegistry类。

以下是我的第一步,但似乎这些对象是延迟初始化的。我可以改变它吗?或者我应该在每个子类中使用一个初始化块?(不喜欢这种方法,因为它很容易忘记)。

 abstract class Product {
    val id : Int
 
    constructor(id : Int) {
        println("ctor with id=$id")
        this.id = id
        ProductRegistry.register(this)
    }
    abstract fun getColor(): String
 }

object BrownProduct : Product(2) {
    override fun getColor() = "brown"
}

object ProductRegistry {
    private val registry = HashSet<Product>()

    fun register(product: Product) {
        println("Registering: $product")
        registry.add(product)
    }

    fun fromId(id: Int): Product {
        return registry.filter { it.id == id }.first()
    }
}

测试:

class TestProdFac {
    @Test
    fun test1() {
        // println(RedProduct) <- if called before, then test works
        assertEquals(RedProduct::class, ProductRegistry.fromId(1)::class)
    }
}

标签: design-patternskotlin

解决方案


不幸的是,如果没有一些额外的工作,我认为这是不可能的。我能想到的主要可能性是:

  1. 使用Java 标准库提供的标准ServiceLoader 。这是官方的做事方式,但需要创建一个清单文件列出实现(此处的文档)或在 Java 9 模块信息中指定实现,我认为 Kotlin 不支持。为避免这种情况,您可以使用Google 的自动服务kapt,允许您简单地注释您的实现以在编译时生成服务元数据。

  2. 您可以手动扫描所有实现的类路径。这还有其他各种缺点:要实现更多的工作,需要对类路径做出假设,并且速度较慢(因为您需要查找并检查每个类)。还有其他问题详细介绍了这种方法,例如这个

  3. 编写您自己的 Kotlin 编译器插件来生成服务元数据,并与第一个选项一起使用。这将需要最多的工作来设置,但使用起来最少。不幸的是,据我所知,关于编译器插件的信息并不多——最好的办法可能是检查官方编译器插件的源代码——比如allopen

最终,我会推荐使用 ServiceLoader、kapt 和自动服务,如第一点所述。设置起来相对简单——如果你使用 Gradle,你只需要添加 kapt 插件和自动服务依赖项(仅在编译时需要),然后@AutoService在你的实现中使用注释。它也没有第二种选择的缺点。


推荐阅读