Patron Singleton
Fait partie de la section Programmation orientée objet du Journey Kotlin de Coddy. Leçon 42 sur 57.
Un motif de conception est une solution éprouvée à un problème de conception qui se présente encore et encore. Le motif singleton garantit qu’une classe possède exactement une instance accessible à l’ensemble du programme. En Kotlin, une déclaration object est un singleton : elle est créée une seule fois, lors de la première utilisation, et ce, en toute sécurité même lorsque plusieurs threads démarrent simultanément :
object Settings {
var theme = "light"
var fontSize = 14
}
fun openEditor() = "editor in ${Settings.theme} theme"Dans main :
Settings.theme = "dark"
println(openEditor())
println(Settings.fontSize)Sortie :
editor in dark theme
14Le singleton contient un état partagé par différentes parties du programme. Gardez cet état privé et ne le modifiez qu’au moyen de méthodes, afin que chaque modification respecte les mêmes règles :
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")À l’intérieur de main :
home(); about(); home()
println(Metrics.report())Sortie :
home=2, about=1Lorsque l’instance unique a besoin de données de configuration, une classe dotée d’un private constructor peut les créer dans son objet compagnon lors de la première requête, puis renvoyer la même instance par la suite :
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 }
}
}Dans main :
val a = Database.connect("db://main")
val b = Database.connect("db://other")
println(a === b)
println(b.url)Résultat :
true
db://mainUtilisez les singletons avec parcimonie. Un singleton est un état global : n’importe quel code peut le modifier, ce qui dissimule les éléments qui en dépendent, et les tests ne peuvent pas démarrer avec une copie vierge. Ils conviennent à ce qui est réellement unique par programme, comme la configuration ou un logger. Lorsqu’une seule partie du programme a besoin d’un objet, transmettez-le comme paramètre, en le typant comme une interface, afin qu’un test puisse transmettre un substitut :
interface Clock { fun now(): Long }
object SystemClock : Clock { override fun now() = System.currentTimeMillis() }
class Session(private val clock: Clock) { // la dépendance est visible
val started = clock.now()
}
val session = Session(SystemClock) // un test passe une fausse ClockDéfi
FacileComplétez le singleton Logger. Il possède un minLevel modifiable (debug, info, warn ou error, dans cet ordre). log(level, message) stocke [WARN] disk low lorsque le niveau est au moins égal à minLevel et, sinon, comptabilise le message dans dropped, que le code externe peut lire mais pas modifier. entries est une liste en lecture seule des lignes stockées, et count(level) renvoie le nombre de lignes stockées correspondant à ce niveau.
Les fonctions auxiliaires de Functions.kt sont fournies : elles accèdent au même Logger depuis un autre fichier. Le code fourni lit le niveau minimal, puis les lignes level,message, et affiche chaque entrée ainsi que errors: 1, dropped: 2.
Votre code doit être placé dans Logger.kt et Functions.kt. Main.kt contient le code fourni pour les entrées et sorties et ne peut pas être modifié.
Essayez vous-même
fun main() {
// Code d'entrée/sortie fourni : le garder tel quel
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}")
}
Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.
Toutes les leçons de Programmation orientée objet
1Classes et objets
Travailler avec des fichiersDéfinir des classesConstructeurs primairesBlocs init et validationConstructeurs secondairesRécapitulatif - Carte de bibliothèque4Héritage
Classes ouvertes et sous-classesRedéfinition des membressuper et constructeursClasses abstraitesRécapitulatif – Annuaire du personnel2Propriétés et encapsulation
Accesseurs personnalisésModificateurs de visibilitéSetters privéslazy et lateinitRécapitulatif - Compte bancaire5Interfaces
Déclarer des interfacesMéthodes par défautInterfaces multiplesInterfaces en tant que typesRécapitulatif - Méthodes de paiement8Génériques
Fonctions génériquesClasses génériquesContraintes de typeVariance avec in et outRécapitulatif - File de priorité11Projet : Gestion de bibliothèque
Livres et membresEmprunter des livresEntraînez-vous par vous-même : Playground Kotlin