确保一个类只需一个实例,并供应对其的大局访问点。
规划方式,单例方式。

一、什么是单例?

在我们初步深入研究结束细节之前,我们要简要评论单例方式及其用法。这种方式确保一个类只需一个实例,并供应对其的大局访问。

这种方式的有点是,它容许轻松访问方针,并且您不需求考虑方针的声明周期。晦气的一面是,很难在多线程程序中“正确”结束它,这使得检验/模仿变得愈加困难。尤其是毕竟一点,便是为什么许多人实践大将这种方式视为“反方式”的原因。您可以运用依托注入结构来假造单例方式。

例如,该方式的常见用例是大局可访问的配备类。然后,在我们的示例中,我们将运用更杂乱的用例。它与博格方式有一些相似之处。

1.1、什么时候运用单例

StackOverflow中有一个有趣的评论。它给出了何时运用此规划方式的一些很好的见地。

Kotlin规划方式之Singleton

“Logger示例”或许是完全满意这种规划方式的少数示例之一。

1.2、比如

一个常见的用例是当您的运用程序中有数据库时。一般您需求控制对此数据库的访问(例如线程问题)。您可以经过供应毕竟接入点来做到这一点。在我们的单例示例中,我们将供应一个名为Storage的类。该类运用与SqlQuery方针。每个客户端(例如Person类)都需求创建和配备一个SqlQuery方针并调用Storage类的执行函数。为了说明当时的设置,我们创建了一个简略的UML图。

Kotlin规划方式之Singleton

二、方针/伴生方针

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图闪现了这种联络。

Kotlin规划方式之Singleton

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正确闪现了方针类。

Kotlin规划方式之Singleton