我试图基于this article在Kotlin中实现一个简单的服务定位器,但我想使用泛型类型参数。我还试图避免将函数与类型化参数内联,因为这要求所有内容都是公开的。
此类负责缓存已定位服务的实例:
class Cache {
private val services: MutableList<Any> = mutableListOf()
fun <T> getService(serviceClass: Class<T>): T? {
val service = services.firstOrNull { s -> s::class.java == serviceClass }
if (service != null) {
return service as T
}
return null
}
fun addService(service: Any) {
services.add(service)
}
}
这是调用缓存的方式:
cache.getService(IMyService::class)
无论它是否包含MyService
的实例,它每次都返回null。问题出在s::class.java == serviceClass
上,因为在运行时缓存包含实例MyService
,并且MyService::class.java
不等于IMyService::class
(MyService::class
也不是-我尝试过也是如此)。
我试图像这样修改getService
方法:
fun <T> getService(): T? {
val service = services.firstOrNull { s -> s is T }
if (service != null) {
return service as T
}
return null
}
在s is T
上,编译器抱怨“无法检查擦除类型T的实例”。我该如何在不进行内联的情况下进行这项工作,而这需要将服务列表公开?
答案 0 :(得分:4)
如果您对反射没问题,则可以使用isAssignableFrom
来检查请求的Class
是否是已缓存的给定Class
的超类/超接口:>
fun <T> getService(serviceClass: Class<T>): T? {
return services.firstOrNull { s -> serviceClass.isAssignableFrom(s::class.java) } as T?
}
答案 1 :(得分:2)
只需回答关于内联的另一半,您就可以避免使用@PublishedApi
批注并标记字段internal
来公开服务地图。例如:
class Cache {
@PublishedApi internal val services: MutableList<Any> = mutableListOf()
inline fun <reified T> getService(serviceClass: Class<T>): T? {
return T::class.java.let { it.cast(services.firstOrNull(it::isInstance)) }
}
fun addService(service: Any) {
services.add(service)
}
}
答案 2 :(得分:2)
通过kcoppock的@PublishedApi
注释,您也可以使用is
,从而避免了反射,并使函数非常简洁:
class Cache {
@PublishedApi internal val services: MutableList<Any> = mutableListOf()
inline fun <reified T> getService() = services.firstOrNull { it is T } as T?
// ...
}
答案 3 :(得分:0)
如果您想保持services
私有(或要使getService
在Java中可用),并且仍然保持Kotlin的更好可用性,只需添加一个重载即可:
fun <T> getService(serviceClass: Class<T>): T? { ... }
inline fun <reified T> getService(): T? = getService(T::class.java)