Nella maggior parte del codice Kotlin maturo, troverai uno di questi schemi qui sotto. L'approccio che utilizza i delegati di proprietà sfrutta la potenza di Kotlin per produrre il codice più piccolo.
Nota: il codice qui è per java.util.Logging
ma la stessa teoria si applica a qualsiasi libreria di registrazione
Static-like (comune, equivalente del codice Java nella domanda)
Se non puoi fidarti delle prestazioni di quella ricerca di hash all'interno del sistema di registrazione, puoi ottenere un comportamento simile al tuo codice Java usando un oggetto associato che può contenere un'istanza e sembrare statico per te.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
creazione di output:
26 dic 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass
foo INFO: Ciao da MyClass
Maggiori informazioni sugli oggetti associati qui: Oggetti Companion ... Si noti inoltre che nell'esempio precedente MyClass::class.java
ottiene l'istanza di tipo Class<MyClass>
per il logger, mentre this.javaClass
otterrebbe l'istanza di tipo Class<MyClass.Companion>
.
Per istanza di una classe (comune)
Ma non c'è davvero alcun motivo per evitare di chiamare e ottenere un logger a livello di istanza. Il modo idiomatico di Java che hai citato è obsoleto e basato sulla paura delle prestazioni, mentre il logger per classe è già memorizzato nella cache da quasi ogni ragionevole sistema di registrazione sul pianeta. Basta creare un membro per contenere l'oggetto logger.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
creazione di output:
26 dic 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Hello from MyClass
Puoi testare le prestazioni sia per istanza che per variazioni di classe e vedere se esiste una differenza realistica per la maggior parte delle app.
Delegati di proprietà (comuni, i più eleganti)
Un altro approccio, suggerito da @Jire in un'altra risposta, è quello di creare un delegato di proprietà, che è quindi possibile utilizzare per eseguire la logica in modo uniforme in qualsiasi altra classe desiderata. C'è un modo più semplice per farlo poiché Kotlin fornisce Lazy
già un delegato, possiamo semplicemente inserirlo in una funzione. Un trucco qui è che se vogliamo conoscere il tipo di classe che attualmente utilizza il delegato, ne facciamo una funzione di estensione su qualsiasi 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"
Questo codice assicura inoltre che se lo si utilizza in un oggetto associato, il nome del logger sarà lo stesso di quello utilizzato per la classe stessa. Ora puoi semplicemente:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
per istanza per classe o se si desidera che sia più statico con un'istanza per classe:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
E il tuo output dal chiamare foo()
entrambe queste classi sarebbe:
26 dic 2015 11:30:55 org.stackoverflow.kotlin.test.Qualcosa di foo INFO: Hello from Something
26 dic 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Hello from SomethingElse
Funzioni di estensione (non comuni in questo caso a causa dell '"inquinamento" di qualsiasi spazio dei nomi)
Kotlin ha alcuni trucchi nascosti che ti consentono di ridurre ulteriormente questo codice. È possibile creare funzioni di estensione sulle classi e quindi fornire loro funzionalità aggiuntive. Un suggerimento nei commenti sopra era di estendere Any
con una funzione logger. Questo può creare rumore ogni volta che qualcuno utilizza il completamento del codice nel proprio IDE in qualsiasi classe. Ma c'è un vantaggio segreto nell'estensione Any
o in qualche altra interfaccia marker: puoi implicare che stai estendendo la tua classe e quindi rilevare la classe in cui ti trovi. Eh? Per essere meno confuso, ecco il codice:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Ora all'interno di una classe (o oggetto associato), posso semplicemente chiamare questa estensione sulla mia classe:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Produzione di output:
26 dic 2015 11:29:12 org.stackoverflow.kotlin.test.Qualcosa di diverso foo INFO: Hello from SomethingDifferent
Fondamentalmente, il codice è visto come una chiamata all'estensione Something.logger()
. Il problema è che ciò che segue potrebbe anche essere vero creando "inquinamento" su altre classi:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Funzioni di estensione su Marker Interface (non sono sicuro di quanto comune, ma modello comune per "tratti")
Per rendere più pulito l'uso delle estensioni e ridurre "l'inquinamento", è possibile utilizzare un'interfaccia marcatore per estendere:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
O anche rendere il metodo parte dell'interfaccia con un'implementazione predefinita:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
E usa una di queste variazioni nella tua classe:
class MarkedClass: Loggable {
val LOG = logger()
}
Produzione di output:
26 dic 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Hello from MarkedClass
Se si desidera forzare la creazione di un campo uniforme per contenere il logger, durante l'utilizzo di questa interfaccia è possibile richiedere facilmente all'implementatore un campo come LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Ora l'implementatore dell'interfaccia deve assomigliare a questo:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Naturalmente, una classe base astratta può fare lo stesso, avendo l'opzione sia dell'interfaccia sia di una classe astratta che implementa tale interfaccia consente flessibilità e 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!
}
}
Mettere tutto insieme (una piccola libreria di supporto)
Ecco una piccola libreria di helper per rendere facile usare una delle opzioni sopra. È comune in Kotlin estendere le API per renderle più di tuo gradimento. Nelle funzioni di estensione o di livello superiore. Ecco un mix per darti opzioni su come creare logger e un esempio che mostra tutte le varianti:
// 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()
}
Scegli quello che vuoi conservare, e qui ci sono tutte le opzioni in uso:
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")
}
}
Tutte le 13 istanze dei logger creati in questo esempio produrranno lo stesso nome di logger e produrranno:
26 dic 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Hello from MixedBagOfTricks
Nota: il unwrapCompanionClass()
metodo assicura che non generiamo un logger che prende il nome dall'oggetto compagno ma piuttosto dalla classe che lo racchiude. Questo è l'attuale metodo consigliato per trovare la classe contenente l'oggetto associato. L'eliminazione di " $ Companion " dal nome utilizzando removeSuffix()
non funziona poiché agli oggetti companion possono essere assegnati nomi personalizzati.