Dans la majorité du code Kotlin mature, vous trouverez l'un de ces modèles ci-dessous. L'approche utilisant les délégués de propriété tire parti de la puissance de Kotlin pour produire le plus petit code.
Remarque: le code ici est pour java.util.Logging
mais la même théorie s'applique à toute bibliothèque de journalisation
Statique (commun, équivalent de votre code Java dans la question)
Si vous ne pouvez pas faire confiance aux performances de cette recherche de hachage dans le système de journalisation, vous pouvez obtenir un comportement similaire à votre code Java en utilisant un objet compagnon qui peut contenir une instance et vous sembler statique.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
création de sortie:
26 décembre 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass
foo INFO: Bonjour de MyClass
Plus d'informations sur les objets compagnons ici: Objets compagnons ... Notez également que dans l'exemple ci-dessus MyClass::class.java
obtient l'instance de type Class<MyClass>
pour le logger, alors que this.javaClass
cela obtiendrait l'instance de type Class<MyClass.Companion>
.
Par instance d'une classe (commun)
Mais il n'y a vraiment aucune raison d'éviter d'appeler et d'obtenir un enregistreur au niveau de l'instance. La méthode Java idiomatique que vous avez mentionnée est obsolète et basée sur la peur des performances, alors que l'enregistreur par classe est déjà mis en cache par presque tous les systèmes de journalisation raisonnables sur la planète. Créez simplement un membre pour contenir l'objet enregistreur.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
création de sortie:
26 décembre 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Bonjour de MyClass
Vous pouvez tester les performances des variations par instance et par classe et voir s'il existe une différence réaliste pour la plupart des applications.
Délégués de propriété (communs, les plus élégants)
Une autre approche, suggérée par @Jire dans une autre réponse, consiste à créer un délégué de propriété, que vous pouvez ensuite utiliser pour appliquer la logique uniformément dans toute autre classe de votre choix. Il existe un moyen plus simple de le faire puisque Kotlin fournit Lazy
déjà un délégué, nous pouvons simplement l'envelopper dans une fonction. Une astuce ici est que si nous voulons connaître le type de la classe utilisant actuellement le délégué, nous en faisons une fonction d'extension sur n'importe quelle classe:
fun <R : Any> R.logger(): Lazy<Logger> {
return lazy { Logger.getLogger(unwrapCompanionClass(this.javaClass).name) }
}
// see code for unwrapCompanionClass() below in "Putting it all Together section"
Ce code garantit également que si vous l'utilisez dans un objet compagnon, le nom de l'enregistreur sera le même que si vous l'aviez utilisé sur la classe elle-même. Maintenant, vous pouvez simplement:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
pour chaque instance de classe, ou si vous voulez qu'elle soit plus statique avec une instance par classe:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
Et votre résultat de l'appel foo()
à ces deux classes serait:
26 décembre 2015 11:30:55 AM org.stackoverflow.kotlin.test Quelque chose foo INFO: Bonjour de Something
26 décembre 2015 11:30:55 AM org.stackoverflow.kotlin.test.SomethingElse foo INFO: Bonjour de SomethingElse
Fonctions d'extension (rare dans ce cas en raison de la "pollution" de n'importe quel espace de noms)
Kotlin a quelques astuces cachées qui vous permettent de rendre une partie de ce code encore plus petite. Vous pouvez créer des fonctions d'extension sur les classes et ainsi leur donner des fonctionnalités supplémentaires. Une suggestion dans les commentaires ci-dessus était d'étendre Any
avec une fonction d'enregistrement. Cela peut créer du bruit à chaque fois que quelqu'un utilise la complétion de code dans son IDE dans n'importe quelle classe. Mais il y a un avantage secret à étendre Any
ou à une autre interface de marqueur: vous pouvez impliquer que vous étendez votre propre classe et donc détectez la classe dans laquelle vous vous trouvez. Hein? Pour être moins déroutant, voici le code:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Maintenant, dans une classe (ou un objet compagnon), je peux simplement appeler cette extension sur ma propre classe:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Production de sortie:
26 décembre 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Bonjour de SomethingDifferent
Fondamentalement, le code est considéré comme un appel à l'extension Something.logger()
. Le problème est que ce qui suit pourrait également être vrai créant de la «pollution» sur d'autres classes:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Fonctions d'extension sur l'interface des marqueurs ( je ne sais pas à quel point il est courant, mais modèle commun pour les «traits»)
Pour rendre l'utilisation des extensions plus propre et réduire la "pollution", vous pouvez utiliser une interface de marqueur pour étendre:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Ou même intégrez la méthode à l'interface avec une implémentation par défaut:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Et utilisez l'une de ces variantes dans votre classe:
class MarkedClass: Loggable {
val LOG = logger()
}
Production de sortie:
26 décembre 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO: Bonjour de MarkedClass
Si vous vouliez forcer la création d'un champ uniforme pour contenir l'enregistreur, alors en utilisant cette interface, vous pourriez facilement demander à l'implémenteur d'avoir un champ tel que LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Maintenant, l'implémenteur de l'interface doit ressembler à ceci:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Bien sûr, une classe de base abstraite peut faire la même chose, ayant l'option à la fois de l'interface et d'une classe abstraite implémentant cette interface permet la flexibilité et l'uniformité:
abstract class WithLogging: Loggable {
override val LOG: Logger = logger()
}
// using the logging from the base class
class MyClass1: WithLogging() {
// ... already has logging!
}
// providing own logging compatible with marker interface
class MyClass2: ImportantBaseClass(), Loggable {
// ... has logging that we can understand, but doesn't change my hierarchy
override val LOG: Logger = logger()
}
// providing logging from the base class via a companion object so our class hierarchy is not affected
class MyClass3: ImportantBaseClass() {
companion object : WithLogging() {
// we have the LOG property now!
}
}
Mettre tout ensemble (une petite bibliothèque d'aide)
Voici une petite bibliothèque d'aide pour rendre l'une des options ci-dessus facile à utiliser. Il est courant dans Kotlin d'étendre les API pour les rendre plus à votre goût. Soit dans les fonctions d'extension ou de niveau supérieur. Voici un mélange pour vous donner des options sur la façon de créer des enregistreurs, et un exemple montrant toutes les variations:
// Return logger for Java class, if companion object fix the name
fun <T: Any> logger(forClass: Class<T>): Logger {
return Logger.getLogger(unwrapCompanionClass(forClass).name)
}
// unwrap companion class to enclosing class given a Java Class
fun <T : Any> unwrapCompanionClass(ofClass: Class<T>): Class<*> {
return ofClass.enclosingClass?.takeIf {
ofClass.enclosingClass.kotlin.companionObject?.java == ofClass
} ?: ofClass
}
// unwrap companion class to enclosing class given a Kotlin Class
fun <T: Any> unwrapCompanionClass(ofClass: KClass<T>): KClass<*> {
return unwrapCompanionClass(ofClass.java).kotlin
}
// Return logger for Kotlin class
fun <T: Any> logger(forClass: KClass<T>): Logger {
return logger(forClass.java)
}
// return logger from extended class (or the enclosing class)
fun <T: Any> T.logger(): Logger {
return logger(this.javaClass)
}
// return a lazy logger property delegate for enclosing class
fun <R : Any> R.lazyLogger(): Lazy<Logger> {
return lazy { logger(this.javaClass) }
}
// return a logger property delegate for enclosing class
fun <R : Any> R.injectLogger(): Lazy<Logger> {
return lazyOf(logger(this.javaClass))
}
// marker interface and related extension (remove extension for Any.logger() in favour of this)
interface Loggable {}
fun Loggable.logger(): Logger = logger(this.javaClass)
// abstract base class to provide logging, intended for companion objects more than classes but works for either
abstract class WithLogging: Loggable {
val LOG = logger()
}
Choisissez celle que vous souhaitez conserver et voici toutes les options utilisées:
class MixedBagOfTricks {
companion object {
val LOG1 by lazyLogger() // lazy delegate, 1 instance per class
val LOG2 by injectLogger() // immediate, 1 instance per class
val LOG3 = logger() // immediate, 1 instance per class
val LOG4 = logger(this.javaClass) // immediate, 1 instance per class
}
val LOG5 by lazyLogger() // lazy delegate, 1 per instance of class
val LOG6 by injectLogger() // immediate, 1 per instance of class
val LOG7 = logger() // immediate, 1 per instance of class
val LOG8 = logger(this.javaClass) // immediate, 1 instance per class
}
val LOG9 = logger(MixedBagOfTricks::class) // top level variable in package
// or alternative for marker interface in class
class MixedBagOfTricks : Loggable {
val LOG10 = logger()
}
// or alternative for marker interface in companion object of class
class MixedBagOfTricks {
companion object : Loggable {
val LOG11 = logger()
}
}
// or alternative for abstract base class for companion object of class
class MixedBagOfTricks {
companion object: WithLogging() {} // instance 12
fun foo() {
LOG.info("Hello from MixedBagOfTricks")
}
}
// or alternative for abstract base class for our actual class
class MixedBagOfTricks : WithLogging() { // instance 13
fun foo() {
LOG.info("Hello from MixedBagOfTricks")
}
}
Les 13 instances des enregistreurs créés dans cet exemple produiront le même nom d'enregistreur et produiront:
26 décembre 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Bonjour de MixedBagOfTricks
Remarque: La unwrapCompanionClass()
méthode garantit que nous ne générons pas un enregistreur nommé d'après l'objet compagnon mais plutôt la classe englobante. Il s'agit de la méthode actuellement recommandée pour rechercher la classe contenant l'objet compagnon. Supprimer " $ Companion " du nom à l'aide de removeSuffix()
ne fonctionne pas car les objets compagnons peuvent recevoir des noms personnalisés.