内容简介:在与类不同,这样
在 Kotlin
中,单例模式被用于替换该编程语言中不存在的 static
成员和字段。 你通过简单地声明 object
以创建一个单例:
object SomeSingleton 复制代码
与类不同, object
不允许有任何构造函数,如果有需要,可以通过使用 init
代码块进行初始化的行为:
object SomeSingleton { init { println("init complete") } } 复制代码
这样 object
将被实例化,并且在初次执行时,其 init
代码块将以线程安全的方式懒惰地执行。 为了这样的效果, Kotlin
对象实际上依赖于 Java
的 静态代码块 。上述Kotlin的 object
将被编译为以下等效的 Java
代码:
public final class SomeSingleton { public static final SomeSingleton INSTANCE; private SomeSingleton() { INSTANCE = (SomeSingleton)this; System.out.println("init complete"); } static { new SomeSingleton(); } } 复制代码
这是在JVM上实现单例的首选方式,因为它可以在线程安全的情况下懒惰地进行初始化,同时不必依赖复杂的双重检查加锁 (double-checked locking)
等加锁算法。 通过在 Kotlin中
简单地使用 object
进行声明,您可以获得安全有效的单例实现。
图:无尽的孤独——单例(译者:作者的描述让我想起了一个悲情的角色,Maiev Shadowsong)
传递一个参数
但是,如果初始化的代码需要一些额外的参数呢?你不能将任何参数传递给它,因为 Kotlin
的 object
关键字不允许存在任何构造函数。
有些情况下,将参数传递给单例初始化代码块是被推荐的方式。 替代方法要求单例类需要知道某些能够获取该参数的外部组件 (component)
,但违反了关注点分离的原则并且使得代码不可被复用。
为了缓解这个问题,该外部组件可以是 依赖注入系统 。这的确是一个具有可行性的解决方案,但您并不总是希望使用这种类型的库——并且,在某些情况下您也无法使用它,就像在接下来的Android示例中我将会所提到的。
在Kotlin中,您必须通过不同的方式去管理单例的另一种情况是,单例的具体实现是由外部 工具 或库(比如 Retrofit
, Room
等等)生成的,它们的实例是通过使用 Builder
模式或 Factory
模式来获取的——在这种情况下,您通常将单例通过 interface
或 abstract class
进行声明,而不是 object
。
一个Android示例
在 Android
平台上,您经常需要将 Context
实例作为参数传递给单例组件的初始化代码块中,以便它们可以获取 文件路径 , 读取系统设置 或 开启Service 等等,但您还希望避免对其进行静态引用(即使是 Application
的静态引用在技术上是安全的)。 有两种方法可以实现这一目标:
- 提前初始化 :在运行任何(几乎)其他代码之前,通过在
Application.onCreate()
中调用初始化所有组件,此时Application
是可用的——这个简单的解决方案的主要缺点是它是通过阻塞主线程的方式来减慢应用程序启动,并初始化了所有组件, 甚至包括那些不会立即使用的组件 。另一个鲜为人知的问题是,在调用此方法之前,Content Provider
也许已经被实例化了(正如文档中所提到的),因此,若Content Provider
使用全局的相关组件,则您必须保证能够在Application.onCreate()
之前初始化该组件,否则您的申请依然可能会导致应用崩溃。 - 延迟初始化 :这是推荐的方法。组件是单例,返回其实例的函数持有
Context
参数。该单例将在第一次调用该函数时使用此参数进行创建和初始化操作。这需要一些同步机制才能保证线程的安全。使用此模式的标准Android
组件的示例是LocalBroadcastManager
:
LocalBroadcastManager.getInstance(context).sendBroadcast(intent) 复制代码
可复用的Kotlin实现方式
我们可以通过封装逻辑来懒惰地在 SingletonHolder
类中创建和初始化带有参数的单例。
为了使该逻辑的线程安全,我们需要实现一个同步算法,它是最有效的算法,同时也是最难做到的——它就是 双重检查锁定算法 (double-checked locking algorithm)
。
open class SingletonHolder<out T, 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 } } } } 复制代码
请注意,为了使算法正常工作,这里需要将 @Volatile
注解对 instance
成员进行标记。
这可能不是最紧凑或优雅的 Kotlin
代码,但它是为双重检查锁定算法生成最行之有效的代码。请信任 Kotlin
的作者:实际上,这些代码正是从 Kotlin
标准库中的lazy() 函数的实现中直接借用的,默认情况下它是同步的。它已被修改为允许将参数传递给 creator
函数。
有鉴于其相对的复杂性,它不是您想要多次编写(或者阅读)的那种代码,实际上其目标是,让您每次必须使用参数实现单例时,都能够重用该 SingletonHolder
类进行实现。
声明 getInstance()
函数的逻辑位置在singleton类的伴随对象内部,这允许通过简单地使用单例类名作为限定符来调用它,就好像 Java
中的静态方法一样。 Kotlin
的伴随对象提供的一个强大功能是它也能够像任何其他对象一样从基类继承,从而实现与仅静态继承相当的功能。
在这种情况下,我们希望使用 SingletonHolder
作为单例类的伴随对象的基类,以便在单例类上重用并自动公开其 getInstance()
函数。
对于 SingletonHolder
类构造方法中的 creator
参数,它是一个函数类型,您可以声明为一个内联 (inline)
的lambda,但更常用的情况是 作为一个函数引用的依赖交给构造器 ,最终其代码如下所示:
class Manager private constructor(context: Context) { init { // Init using context argument } companion object : SingletonHolder<Manager, Context>(::Manager) } 复制代码
现在可以使用以下语法调用单例,并且它的初始化将是 lazy
并且线程安全的:
Manager.getInstance(context).doStuff() 复制代码
当三方库生成单例实现并且 Builder
需要参数时,您也可以使用这种方式,以下是使用 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, "Sample.db") .build() }) } 复制代码
注意:当 Builder
不需要参数时,您只需使用 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) } } } 复制代码
我希望这些代码能够给您带来一些启发。如果您有建议或疑问,请不要犹豫,在评论部分开始讨论,感谢您的阅读!
--------------------------广告分割线------------------------------
关于我
Hello,我是 却把清梅嗅 ,如果您觉得文章对您有价值,欢迎 :heart:,也欢迎关注我的博客或者 Github 。
如果您觉得文章还差了那么点东西,也请通过 关注 督促我写出更好的文章——万一哪天我进步了呢?
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 局限性
- 谈谈 Kubernetes 的问题和局限性
- 3分钟干货之微服务架构的局限性
- 深度学习并不完美,它的局限性是什么?
- 深度学习局限何在?图网络的出现并非偶然
- 中科院自动化所副所长刘成林:一窥模式识别背后的人工智能局限
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。