En la mayoría de los códigos Kotlin maduros, encontrará uno de estos patrones a continuación. El enfoque que usa Delegados de propiedades aprovecha el poder de Kotlin para producir el código más pequeño.
Nota: el código aquí es para java.util.Logging
pero la misma teoría se aplica a cualquier biblioteca de registro
Estática (común, equivalente a su código Java en la pregunta)
Si no puede confiar en el rendimiento de esa búsqueda hash dentro del sistema de registro, puede obtener un comportamiento similar a su código Java mediante el uso de un objeto complementario que puede contener una instancia y sentirse como estático para usted.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
creando salida:
26 de diciembre de 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass
foo INFO: Hola desde MyClass
Más información sobre los objetos complementarios aquí: Objetos complementarios ... También tenga en cuenta que en el ejemplo anterior MyClass::class.java
obtiene la instancia de tipo Class<MyClass>
para el registrador, mientras this.javaClass
que obtendría la instancia de tipo Class<MyClass.Companion>
.
Por instancia de una clase (común)
Pero, realmente no hay razón para evitar llamar y obtener un registrador a nivel de instancia. La forma idiomática de Java que mencionó está desactualizada y se basa en el miedo al rendimiento, mientras que el registrador por clase ya está almacenado en caché por casi cualquier sistema de registro razonable en el planeta. Simplemente cree un miembro para contener el objeto registrador.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
creando salida:
26 de diciembre de 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Hola desde MyClass
Puede realizar pruebas de rendimiento tanto por variaciones de instancia como de clase y ver si hay una diferencia realista para la mayoría de las aplicaciones.
Delegados de propiedad (común, más elegante)
Otro enfoque, sugerido por @Jire en otra respuesta, es crear un delegado de propiedades, que luego puede usar para hacer la lógica de manera uniforme en cualquier otra clase que desee. Hay una manera más simple de hacer esto ya que Kotlin ya proporciona un Lazy
delegado, simplemente podemos envolverlo en una función. Un truco aquí es que si queremos saber el tipo de clase que actualmente usa el delegado, lo convertimos en una función de extensión en cualquier clase:
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"
Este código también se asegura de que si lo usa en un Objeto complementario, el nombre del registrador será el mismo que si lo usara en la clase misma. Ahora puedes simplemente:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
para cada instancia de clase, o si desea que sea más estático con una instancia por clase:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
Y su resultado de llamar foo()
a estas dos clases sería:
26 de diciembre de 2015 11:30:55 am org.stackoverflow.kotlin.test.Something foo INFO: Hello from Something
26 de diciembre de 2015 11:30:55 am org.stackoverflow.kotlin.test.SomethingElse foo INFO: Hola de SomethingElse
Funciones de extensión (poco común en este caso debido a la "contaminación" de cualquier espacio de nombres)
Kotlin tiene algunos trucos ocultos que le permiten hacer que parte de este código sea aún más pequeño. Puede crear funciones de extensión en clases y, por lo tanto, darles funcionalidad adicional. Una sugerencia en los comentarios anteriores fue extender Any
con una función de registrador. Esto puede crear ruido cada vez que alguien usa la finalización de código en su IDE en cualquier clase. Pero existe un beneficio secreto al extender Any
u otra interfaz de marcador: puede implicar que está extendiendo su propia clase y, por lo tanto, detectar la clase en la que se encuentra. ¿Eh? Para ser menos confuso, aquí está el código:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Ahora dentro de una clase (u objeto complementario), simplemente puedo llamar a esta extensión en mi propia clase:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Producción de salida:
26 de diciembre de 2015 11:29:12 am org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Hola de SomethingDifferent
Básicamente, el código se ve como una llamada a la extensión Something.logger()
. El problema es que lo siguiente también podría ser cierto creando "contaminación" en otras clases:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Funciones de extensión en la interfaz de marcador (no estoy seguro de qué tan común, pero modelo común para "rasgos")
Para hacer que el uso de extensiones sea más limpio y reducir la "contaminación", puede usar una interfaz de marcador para extender:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
O incluso haga que el método forme parte de la interfaz con una implementación predeterminada:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Y use cualquiera de estas variaciones en su clase:
class MarkedClass: Loggable {
val LOG = logger()
}
Producción de salida:
26 de diciembre de 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Hola de MarkedClass
Si desea forzar la creación de un campo uniforme para contener el registrador, al usar esta interfaz, podría requerir fácilmente que el implementador tenga un campo como LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Ahora el implementador de la interfaz debe verse así:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Por supuesto, una clase base abstracta puede hacer lo mismo, teniendo la opción de que tanto la interfaz como una clase abstracta que implementen esa interfaz permitan flexibilidad y uniformidad:
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!
}
}
Poniendo todo junto (una pequeña biblioteca auxiliar)
Aquí hay una pequeña biblioteca auxiliar para hacer que cualquiera de las opciones anteriores sea fácil de usar. Es común en Kotlin extender las API para que sean más de su agrado. Ya sea en extensión o funciones de nivel superior. Aquí hay una combinación para darle opciones sobre cómo crear registradores, y una muestra que muestra todas las variaciones:
// 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()
}
Elija cualquiera de los que desea conservar, y aquí están todas las opciones en 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")
}
}
Las 13 instancias de los registradores creados en este ejemplo producirán el mismo nombre de registrador y salida:
26 de diciembre de 2015 11:39:00 AM org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Hola de MixedBagOfTricks
Nota: El unwrapCompanionClass()
método garantiza que no generemos un registrador con el nombre del objeto complementario, sino más bien la clase adjunta. Esta es la forma recomendada actual para encontrar la clase que contiene el objeto complementario. Eliminar " $ Companion " del nombre usando removeSuffix()
no funciona ya que los objetos complementarios pueden recibir nombres personalizados.