Na maioria dos códigos Kotlin maduros, você encontrará um desses padrões abaixo. A abordagem usando Delegados de Propriedade tira proveito do poder do Kotlin para produzir o menor código.
Nota: o código aqui é para, java.util.Logging
mas a mesma teoria se aplica a qualquer biblioteca de log
Estático (comum, equivalente ao seu código Java na pergunta)
Se você não puder confiar no desempenho dessa pesquisa de hash dentro do sistema de log, poderá obter um comportamento semelhante ao seu código Java usando um objeto complementar que pode conter uma instância e parecer estático para você.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
criando saída:
26 de dezembro de 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass
foo INFO: Olá, do MyClass
Mais sobre objetos complementares aqui: Objetos complementares ... Observe também que na amostra acima MyClass::class.java
obtém a instância do tipo Class<MyClass>
para o criador de logs, enquanto this.javaClass
que a instância do tipo Class<MyClass.Companion>
.
Por instância de uma classe (comum)
Porém, não há realmente nenhuma razão para evitar ligar e obter um criador de logs no nível da instância. A maneira idiomática de Java que você mencionou está desatualizada e baseada no medo de desempenho, enquanto o criador de logs por classe já está armazenado em cache por quase qualquer sistema de criação de log razoável no planeta. Basta criar um membro para armazenar o objeto logger.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
criando saída:
26 de dezembro de 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Olá, do MyClass
Você pode testar o desempenho por variações por instância e por classe e verificar se há uma diferença realista para a maioria dos aplicativos.
Delegados de propriedades (comuns, mais elegantes)
Outra abordagem, sugerida por @Jire em outra resposta, é criar um delegado de propriedade, que você pode usar para executar a lógica de maneira uniforme em qualquer outra classe que desejar. Existe uma maneira mais simples de fazer isso, já que o Kotlin já fornece um Lazy
delegado, podemos apenas envolvê-lo em uma função. Um truque aqui é que, se queremos saber o tipo de classe atualmente usando o delegado, fazemos dela uma função de extensão em qualquer 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"
Esse código também garante que, se você o usar em um Objeto complementar, o nome do criador de logs será o mesmo que se você o utilizasse na própria classe. Agora você pode simplesmente:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
por instância de classe ou se você quiser que seja mais estático com uma instância por classe:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
E sua saída ao chamar foo()
essas duas classes seria:
26 de dezembro de 2015 11:30:55 org.stackoverflow.kotlin.test.Algo foo INFO: Hello from Something
26 de dezembro de 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Olá, de SomethingElse
Funções de extensão (incomum neste caso devido à "poluição" de qualquer espaço para nome)
O Kotlin possui alguns truques ocultos que permitem diminuir ainda mais esse código. Você pode criar funções de extensão nas classes e, portanto, fornecer funcionalidades adicionais. Uma sugestão nos comentários acima foi estender Any
com uma função de logger. Isso pode gerar ruído sempre que alguém usa o preenchimento de código no IDE em qualquer classe. Mas há um benefício secreto em estender Any
ou em alguma outra interface de marcador: você pode sugerir que está estendendo sua própria classe e, portanto, detectar a classe em que está. Hã? Para ser menos confuso, aqui está o 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)
}
Agora, dentro de uma classe (ou objeto complementar), posso simplesmente chamar essa extensão em minha própria classe:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Produção de saída:
26 de dezembro de 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Olá, de SomethingDifferent
Basicamente, o código é visto como uma chamada para extensão Something.logger()
. O problema é que o seguinte também pode ser verdadeiro, criando "poluição" em outras classes:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Funções de extensão na interface do marcador (não tenho certeza de quão comum, mas modelo comum para "características")
Para tornar o uso de extensões mais limpo e reduzir a "poluição", você pode usar uma interface de marcador para estender:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Ou até mesmo torne o método parte da interface com uma implementação padrão:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
E use uma destas variações em sua classe:
class MarkedClass: Loggable {
val LOG = logger()
}
Produção de saída:
26 de dezembro de 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Olá, de MarkedClass
Se você deseja forçar a criação de um campo uniforme para reter o criador de logs, ao usar essa interface, você pode facilmente exigir que o implementador tenha um campo como LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Agora o implementador da interface deve ficar assim:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Obviamente, uma classe base abstrata pode fazer o mesmo, tendo a opção da interface e de uma classe abstrata implementando essa interface, permitindo flexibilidade e uniformidade:
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!
}
}
Juntando tudo (uma pequena biblioteca auxiliar)
Aqui está uma pequena biblioteca auxiliar para facilitar a utilização de qualquer uma das opções acima. É comum no Kotlin estender APIs para torná-las mais ao seu gosto. Nas funções de extensão ou de nível superior. Aqui está uma combinação para fornecer opções de como criar loggers e uma amostra mostrando todas as variações:
// 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()
}
Escolha o que você deseja manter e aqui estão todas as opções em 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")
}
}
Todas as 13 instâncias dos criadores de logs criados nesta amostra produzirão o mesmo nome e a saída:
26 de dezembro de 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Olá, de MixedBagOfTricks
Nota: O unwrapCompanionClass()
método garante que não geremos um criador de logs com o nome do objeto complementar, mas a classe envolvente. Esta é a maneira atualmente recomendada de encontrar a classe que contém o objeto complementar. Retirar " $ Companion " do nome usando removeSuffix()
não funciona, pois os objetos complementares podem receber nomes personalizados.