Menu
Coddy logo textTech

Padrão Singleton

Parte da seção Programação Orientada a Objetos do Journey de Kotlin da Coddy. Lição 42 de 57.

Um padrão de projeto é uma solução comprovada para um problema de projeto que surge repetidamente. O padrão singleton garante que uma classe tenha exatamente uma instância que todo o programa possa acessar. Em Kotlin, uma declaração object é um singleton: ela é criada uma vez, no primeiro uso, e com segurança mesmo quando várias threads são iniciadas ao mesmo tempo:

object Settings {
    var theme = "light"
    var fontSize = 14
}

fun openEditor() = "editor in ${Settings.theme} theme"

Dentro de main:

Settings.theme = "dark"
println(openEditor())
println(Settings.fontSize)

Saída:

editor in dark theme
14

O singleton mantém um estado que diferentes partes do programa compartilham. Mantenha esse estado privado e altere-o somente por meio de métodos, para que toda mudança siga as mesmas regras:

object Metrics {
    private val hits = mutableMapOf<String, Int>()

    fun hit(page: String) { hits[page] = (hits[page] ?: 0) + 1 }
    fun report() = hits.entries.joinToString { "${it.key}=${it.value}" }
}

fun home() = Metrics.hit("home")
fun about() = Metrics.hit("about")

Dentro de main:

home(); about(); home()
println(Metrics.report())

Saída:

home=2, about=1

Quando a instância única precisa de dados de configuração, uma classe com um private constructor pode criá-la em seu objeto companion na primeira solicitação e retornar a mesma instância depois:

class Database private constructor(val url: String) {
    companion object {
        private var instance: Database? = null

        fun connect(url: String): Database =
            instance ?: Database(url).also { instance = it }
    }
}

Dentro de main:

val a = Database.connect("db://main")
val b = Database.connect("db://other")
println(a === b)
println(b.url)

Saída:

true
db://main

Use singletons com moderação. Um singleton é um estado global: qualquer código pode alterá-lo, o que oculta quem depende dele, e os testes não podem começar a partir de uma cópia nova. Eles são adequados para o que realmente existe uma vez por programa, como a configuração ou um logger. Quando apenas uma parte do programa precisa de um objeto, passe-o como parâmetro, tipado como uma interface, para que um teste possa passar um objeto falso em seu lugar:

interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }

class Session(private val clock: Clock) {      // a dependência é visível
    val started = clock.now()
}
val session = Session(SystemClock)             // um teste passa um Clock falso
challenge icon

Desafio

Fácil

Complete o singleton Logger. Ele tem um minLevel alterável (debug, info, warn ou error, nessa ordem). log(level, message) armazena [WARN] disk low quando o nível é pelo menos minLevel e, caso contrário, contabiliza a mensagem em dropped, que pode ser lido fora do código, mas não alterado. entries é uma lista somente para leitura das linhas armazenadas, e count(level) retorna quantas linhas armazenadas têm esse nível.

As funções auxiliares em Functions.kt são fornecidas: elas acessam o mesmo Logger a partir de outro arquivo. O código fornecido lê o nível mínimo e, em seguida, as linhas level,message, e imprime cada entrada e errors: 1, dropped: 2.

Seu código deve estar em Logger.kt e Functions.kt. Main.kt contém o código fornecido de entrada/saída e não pode ser editado.

Experimente você mesmo

fun main() {
    // Código de entrada/saída fornecido: mantenha-o como está
    val input = generateSequence(::readLine).toList()
    Logger.minLevel = input[0]
    for (line in input.drop(1)) {
        val (level, message) = line.split(",", limit = 2)
        when (level) {
            "debug" -> debug(message)
            "info" -> info(message)
            "warn" -> warn(message)
            else -> error(message)
        }
    }
    for (e in Logger.entries) println(e)
    println("errors: ${Logger.count("error")}, dropped: ${Logger.dropped}")
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Playground de Kotlin