在 Kotlin 中,单例模式被用作静态成员和字段的替代方案,因为在该编程语言中不存在这些概念。在 Kotlin 中创建单例仅需声明一个对象。
object SomeSingleton
与类不同,对象不能有任何构造函数,但如果需要一些初始化代码,可以允许 init 块。
object SomeSingleton {
init {
println("init complete")
}
}
当第一次访问时,对象将被懒加载地实例化,其 init 块也会被执行,这一过程是线程安全的。为实现这一点,Kotlin 对象实际上依赖于 Java 的静态初始化块。上述 Kotlin 对象将被编译为以下等效的 Java 代码:
public final class SomeSingleton {
public static final SomeSingleton INSTANCE;
private SomeSingleton() {
INSTANCE = (SomeSingleton)this;
System.out.println("init complete");
}
static {
new SomeSingleton();
}
}
这是在 JVM 上实现单例的首选方式,因为它可以实现线程安全的懒加载,无需依赖复杂的双重检查锁定算法。通过简单地使用 Kotlin 对象声明,你可以保证得到一个安全且高效的单例实现。
但如果初始化代码需要一些额外的参数怎么办?因为 Kotlin 对象不能有任何构造函数,所以你不能向其传递任何参数。
对于某些情况,将参数传递给单例初始化块是推荐的模式。替代方法需要单例类了解一些外部组件以便能够获取该参数,这违反了关注点分离原则,使代码重用性降低。为了缓解这个问题,那个外部组件可以是依赖注入系统。这是一个有效的解决方案,但你可能并不总是想使用那种类型的库,而且在某些情况下,它无法被使用。
我们可以将逻辑封装到一个 SingletonHolder 类中,以懒加载地创建并初始化带参数的单例。为了使该逻辑线程安全,我们需要实现一个同步算法,最有效的算法是双重检查锁定算法。
open class SingletonHolder<out T: Any, in A>(creator: (A) -> T) {
private var creator: ((A) -> T)? = creator
@Volatile private var instance: T? = null
fun getInstance(arg: A): T {
val i = instance
if (i != null) {
return i
}
return synchronized(this) {
val i2 = instance
if (i2 != null) {
i2
} else {
val created = creator!!(arg)
instance = created
creator = null
created
}
}
}
}
这可能不是最紧凑或优雅的 Kotlin 代码,但它产生了双重检查锁定算法最有效的字节码。
最后,带参数的单例的代码看起来像这样:
class Manager private constructor(context: Context) {
init {
// 使用 context 参数进行初始化
}
companion object : SingletonHolder<Manager, Context>(::Manager)
}
现在,可以使用以下语法调用单例,其初始化将是懒加载和线程安全的:
Manager.getInstance(context).doStuff()
你还可以在外部库生成单例实现并且构建器需要一个参数的时候使用这种习语。这里使用 Android 的 Room 持久化库做一个例子:
@Database(entities = arrayOf(User::class), version = 1)
abstract class UsersDatabase : RoomDatabase() {
abstract fun userDao(): UserDao
companion object : SingletonHolder<UsersDatabase, Context>({
Room.databaseBuilder(it.applicationContext,
UsersDatabase::class.java, "UsersDatabase")
.build()
})
}
现在,你可以在任何地方安全地访问数据库,只需要传递一个 Context 参数:
UsersDatabase.getInstance(context).userDao().insert(user)
注意:当构建器不需要参数时,您可以简单地使用 lazy 委托属性来代替:
interface GitHubService {
companion object {
val instance: GitHubService by lazy {
val retrofit = Retrofit.Builder()
.baseUrl("https://api.github.com/")
.build()
retrofit.create(GitHubService::class.java)
}
}
}
这个解决方案不仅适用于 Android,而且在许多其他情况下也可以使用