确保一个类只需一个实例,并供应对其的大局访问点。
规划方式,单例方式。
一、什么是单例?
在我们初步深入研究结束细节之前,我们要简要评论单例方式及其用法。这种方式确保一个类只需一个实例,并供应对其的大局访问。
这种方式的有点是,它容许轻松访问方针,并且您不需求考虑方针的声明周期。晦气的一面是,很难在多线程程序中“正确”结束它,这使得检验/模仿变得愈加困难。尤其是毕竟一点,便是为什么许多人实践大将这种方式视为“反方式”的原因。您可以运用依托注入结构来假造单例方式。
例如,该方式的常见用例是大局可访问的配备类。然后,在我们的示例中,我们将运用更杂乱的用例。它与博格方式有一些相似之处。
1.1、什么时候运用单例
StackOverflow中有一个有趣的评论。它给出了何时运用此规划方式的一些很好的见地。
“Logger示例”或许是完全满意这种规划方式的少数示例之一。
1.2、比如
一个常见的用例是当您的运用程序中有数据库时。一般您需求控制对此数据库的访问(例如线程问题)。您可以经过供应毕竟接入点来做到这一点。在我们的单例示例中,我们将供应一个名为Storage的类。该类运用与SqlQuery方针。每个客户端(例如Person类)都需求创建和配备一个SqlQuery方针并调用Storage类的执行函数。为了说明当时的设置,我们创建了一个简略的UML图。
二、方针/伴生方针
Kotlin中的单例是什么?Kotlin方针是单例吗?
Kotlin言语供应了一些称为Object类结合运用的功用。这些声明旨在以本机方法在Kotlin中结束Singleton类。值得一提的事,方针是以慵懒方法结构的。此外,它们永远不会被损坏,由于他们在运用程序的生命周期内可用。
如安在Kotlin中运用单例? 如上面示例中所述,我们将运用Storage类结束中央访问点。它被声明为方针,因此可以在代码库中大局访问。它有一个与SqlQuery方针一同运用的函数“execute”。在这个函数中,发生了实践的数据库访问。
此外,还可以在那里结束附加逻辑,例如排队或线程访问安全。另一方面,有类Person,它结束了Persistable接口。它创建一个SqlQuery方针并设置所需的SQL查询。人们可以梦想几个结束Persistable接口的不同类。他们所需求做的便是设置正确的SqlQuery方针并在毕竟调用Storage方针。
object Storage {
fun execute(query: SqlQuery) {
}
}
class SqlQuery {
}
interface Persistable {
fun persist()
}
class Person: Persistable {
override fun persist() {
val query = SqlQuery()
Storage.execute(query)
}
}
fun main() {
val person = Person()
person.persist()
}
Kotlin方针也可以在类中运用。在这种情况下,它被称为伴生方针。它的行为方法类似于内部类。如果类中只需某些部分是静态的,这很有用。以下代码是与上述略有不同的结束。存储类别不再是静态的。只需它的函数“execute”是可以静态访问的。请注意,可以按照与以前相同的方法访问“execute”函数。运用伴生方针来结束单例的长处是它容许运用继承。
class Storage {
companion object {
fun execute(query: SqlQuery) {
}
}
}
class Person : Persistable {
override fun persist() {
val query = SqlQuery()
Storage.execute(query)
}
}
2.1、代结构函数/初始化的Kotlin Singleton类
在Kotlin中,Object和其他类一样有一个init块。可是,它不供应结构函数。一般来说,我们不需求专门的施工人员,由于客户不应该担任施工。但在初始化单例之前设置一些配备或许会很有用。在我们的示例中,我们可以梦想数据库是加密的。Storage类在第一次创建时需求暗码才能翻开。
那么如安在Kotlin中将结构函数参数或参数传递给单例呢?
2.2、代参数的Kotlin Singleton类
为了结束参数,我们需求一个一般的类,它有一个伴生方针和私有默默许结构函数。
不才面的结束中,Storage类有一个私有结构函数。所以它不能被任何客户端实例化。它需求一个Config方针,其间包含设置存储的一切参数/参数。随同方针供应了一个getInstance
方法,该方法正在创建单例方针。此方法承受第一次创建静态方针不时运用的可选配备。正如您所看到的,Person方针可以以相同的方法调用Storage类。
我们想强调的是,一般来说,这种方法不是最佳实践。我们无法控制谁将第一次调用Storage类,并且由于一切后续调用都不运用配备方针,因此很难很好地控制配备。
data class Confit(val param: Int = 0)
class Storage private constructor(private val config: Config) {
companion object {
private var instance: Storage? = null
fun getInstance(config: Config = Config()): Storage {
if (instance == null) // Not thread safe!
instance = Storage(config)
return instance!!
}
fun execute(query: SqlQuery) {
getInstance().execute(query)
}
}
fun execute(query: SqlQuery) {
}
}
class Person: Persistable {
override fun persist() {
val query = SqlQuery()
Storage.execute(query)
}
}
我们以为,最好创建一个单独的函数而不是结构函数来配备单例。经过单独的函数,我们可以更好地控制正确的配备,并且也愈加线程安全。或许的结束或许类似于以下代码:
object Storage {
private val config = Config()
fun configure(config: Config) {
}
fun execute(query: SqlQuery) {
}
}
2.3、Lazy
一般,方针自身现已以慵懒方法实例化。因此,只需在第一次调用时,它们才需求内存。可是我们甚至可以经过增加lazy关键字来使成员变量慵懒实例化。
object Storage {
private val heavyData: Int by laze() { 5 }
fun execute(query: SqlQuery) {
println("Storage execute")
}
}
2.4、线程安全
Kotlin中的单例线程安全吗?
Kotlin 的参阅页 (LINK) 指出“方针声明的初始化是线程安全的,并且在初次访问时结束”。
三、从Java访问
Kotlin和Java可以在代码库中混合。因此可以在Java中运用Kotlin Singleton方针,反之亦然。Kotlin自动供应一个名为INSTANCE
的静态字段,可以在Java代码中引用它。我们上面的比如可以用Java访问,例如:
public class JavaMain {
public static void main(String[] args) {
SqlQuery query = new SqlQuery()
Storage.INSTANCE.execute(query)
}
}
四、依托注入
运用单例的首要缺点之一是难以检验这些运用单例的类。原因是客户端与Kotlin方针的结束存在的紧密耦合。
4.1、如安在Kotlin中检验单例类?
如果您在函数中运用一般类和随同对方针,则可以运用继承版别替换实践方针。我们将更改Storage类,使其结束Storage接口。第二种结束(称为MockStorage)也结束了该接口。存储类自身有一个私有结构函数并保存公共伴生方针。运用的“实例”属于IStorage类型,因此可以替换。下面的UML图闪现了这种联络。
interface IStorage {
fun execute(query: SqlQuery)
}
open class Storage private constructor(): IStorage {
companion object {
private var instance: IStorage? = null
fun getInstance(): IStorage {
if (instance == null) // Not thread safe!
instance = Storage()
return instance!!
}
fun setInstance(storage: IStorage) {
instance = storage
}
fun execute(query: SqlQuery) {
getInstance().execute(query)
}
}
override fun execute(query: SqlQuery) {
println("Default implementation")
}
}
class MockStorage: IStorage {
override fun execute(query: SqlQuery) {
println("Mocked implementation")
}
}
fun main() {
val testStorage = MockStorage()
Storage.setInstance(testStorage)
val person = Person()
person.persist()
}
这种方法的长处是您可以完全控制代码并且不依托于任何其他库。但缺点是您需求确保线程访问完全确保安全。
4.2、 KODEIN – Kotlin依托注入结构
KODEIN是一个十分有用的依托注入/检索容器,它十分易于运用和配备。它为您想要注入的方针供应了一层抽象。激烈建议您检查这个库,由于它供应了良好的DSL言语,并且速度快且经过优化。当时,您需求习气这个库,并且需求处理对此库的另一种依托联络。毕竟,您的大多数类/模块将依托于这个结构。
我们现已调整了我们的示例,以便Person类乣一个KODEIN方针。此KODEIN方针供应可以按类型检索/映射的依托联络。很快乐看到我们现在可以将方针与其依托联络完全解耦。
open class Storage {
open fun execute(query: SqlQuery) {
println("Storage execute")
}
}
class MockStorage: Storage() {
override fun execute(query: SqlQuery) {
println("MockStorage execute")
}
}
class Pseron(val kodein: Kodein) : Persistable {
private val storage by kodein.instance<Storage>()
override fun persist() {
val query = SqlQuery()
storage.execute(query)
}
}
fun main() {
val kodein = Kodein {
bind<Storage>() with singeton { MockStorage() }
val person = Person(kodein)
person.persist()
}
}
五、Android运用程序开发
在大多数运用程序中,都会有一些类作为代码进口。在根据前端的运用程序中,例如桌面、iOS或Android运用程序,会有一个类来保存一切视图模型、网管等。
5.1、运用类
这些类之一的是运用程序类。一般,运用程序类是您的冷库中最底子的类。它包含一般事务逻辑和粘合代码。它可以是工厂(例如抽象工厂方法)的供应者、服务器和数据库的网管以及视图模型和控制器的首要访问点。它是维护大局运用程序情况的基类。
特定于Android,此类运用程序类还包含一切活动和服务。
由于此类包含十分大局的信息,因此在Android中供应运用程序类的单例范文是有意义的。
5.2、视图模型
一般,ViewModel不应该是单例方针。它们供应动态数据并绑定到Activity或Fragment的上下文。
六、IDE中的代码结束/语法突出闪现
大多数IDE的确支撑Kotlin原生功用,例如Object和Companion方针关键字。如下图所示,Jetbrains Intellij正确闪现了方针类。