В большинстве зрелых кодов Kotlin вы найдете один из этих шаблонов ниже. Подход, использующий Property Delegates, использует возможности Kotlin для создания наименьшего кода.
Примечание: код здесь для, java.util.Logging
но та же теория применима к любой библиотеке журналов
Статический (обычный, эквивалент вашего Java-кода в вопросе)
Если вы не можете доверять производительности этого поиска по хешу в системе ведения журналов, вы можете получить поведение, подобное вашему Java-коду, используя объект-компаньон, который может содержать экземпляр и чувствовать себя статичным для вас.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
создание вывода:
26 декабря 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass
ИНФОРМАЦИЯ foo: Привет от MyClass
Подробнее о сопутствующих объектах здесь: Companion Objects ... Также обратите внимание, что в приведенном выше примере MyClass::class.java
получает экземпляр типа Class<MyClass>
для регистратора, в то время как this.javaClass
получает экземпляр типа Class<MyClass.Companion>
.
На экземпляр класса (общий)
Но на самом деле нет причин избегать звонков и получения регистратора на уровне экземпляра. Упомянутый вами идиоматический способ Java устарел и основан на страхе перед производительностью, тогда как регистратор для каждого класса уже кэшируется практически любой разумной системой журналирования на планете. Просто создайте член для хранения объекта регистратора.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
создание вывода:
26 декабря 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo ИНФОРМАЦИЯ: Привет из MyClass
Вы можете проверить производительность как для каждого экземпляра, так и для каждого класса, и посмотреть, есть ли реальная разница для большинства приложений.
Имущественные делегаты (обычные, самые элегантные)
Другой подход, предложенный @Jire в другом ответе, заключается в создании делегата свойства, который затем можно использовать для равномерного выполнения логики в любом другом классе, который вы хотите. Есть более простой способ сделать это, поскольку Kotlin уже предоставляет Lazy
делегата, мы можем просто обернуть его в функцию. Здесь есть одна хитрость: если мы хотим узнать тип класса, использующего в настоящее время делегат, мы сделаем его функцией расширения для любого класса:
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"
Этот код также гарантирует, что если вы используете его в объекте-компаньоне, имя регистратора будет таким же, как если бы вы использовали его в самом классе. Теперь вы можете просто:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
для каждого экземпляра класса или если вы хотите, чтобы он был более статичным с одним экземпляром для каждого класса:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
И ваш результат вызова foo()
из обоих этих классов будет:
26 декабря 2015 г. 11:30:55 org.stackoverflow.kotlin.test.Something foo ИНФОРМАЦИЯ: Привет от чего-то
26 декабря 2015 г. 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo ИНФОРМАЦИЯ: Привет от SomethingElse
Функции расширения (необычные в этом случае из-за «загрязнения» любого пространства имен)
У Kotlin есть несколько скрытых трюков, которые позволяют сделать этот код еще меньше. Вы можете создавать функции расширения для классов и, следовательно, предоставлять им дополнительную функциональность. Одним из предложений в комментариях выше было расширение Any
с помощью функции регистрации. Это может создавать шум в любое время, когда кто-либо использует завершение кода в своей среде IDE в любом классе. Но есть секретное преимущество расширения Any
или какого-либо другого интерфейса маркера: вы можете подразумевать, что вы расширяете свой собственный класс и, следовательно, обнаруживаете класс, в котором находитесь. А? Чтобы быть менее запутанным, вот код:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Теперь внутри класса (или объекта-компаньона) я могу просто вызвать это расширение в своем собственном классе:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Производить продукцию:
26 декабря 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo ИНФОРМАЦИЯ: Привет от SomethingDifferent
По сути, код рассматривается как призыв к расширению Something.logger()
. Проблема заключается в том, что следующее также может быть причиной «загрязнения» для других классов:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Функции расширения в интерфейсе маркера (не уверен, насколько распространена, но распространена модель для «черт»)
Чтобы сделать использование расширений более понятным и уменьшить «загрязнение», вы можете использовать интерфейс маркера для расширения:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Или даже сделайте метод частью интерфейса с реализацией по умолчанию:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
И используйте любой из этих вариантов в вашем классе:
class MarkedClass: Loggable {
val LOG = logger()
}
Производить продукцию:
26 декабря 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo ИНФОРМАЦИЯ: Привет от MarkedClass
Если вы хотите принудительно создать единое поле для хранения регистратора, то при использовании этого интерфейса вы можете легко потребовать, чтобы у разработчика было поле, такое как LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Теперь разработчик интерфейса должен выглядеть так:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Конечно, абстрактный базовый класс может делать то же самое, имея опцию как интерфейса, так и абстрактного класса, реализующего этот интерфейс, что обеспечивает гибкость и единообразие:
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!
}
}
Собираем все вместе (небольшая вспомогательная библиотека)
Вот небольшая вспомогательная библиотека, позволяющая легко использовать любой из перечисленных выше вариантов. В Kotlin принято расширять API-интерфейсы, чтобы сделать их по своему вкусу. Либо в расширении, либо в функциях верхнего уровня. Вот микс, чтобы дать вам варианты создания регистраторов, и пример, показывающий все варианты:
// 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()
}
Выберите тот, который вы хотите сохранить, и вот все варианты:
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")
}
}
Все 13 экземпляров регистраторов, созданных в этом образце, будут производить одно и то же имя регистратора и выводить:
26 декабря 2015 г. 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo ИНФОРМАЦИЯ: Привет от MixedBagOfTricks
Примечание: В unwrapCompanionClass()
метод гарантирует , что мы не генерируют регистратор имени объекта - компаньона , а скорее объемлющего класса. В настоящее время это рекомендуемый способ найти класс, содержащий объект-компаньон. Удаление « $ Companion » из имени с помощью removeSuffix()
не работает, поскольку объектам-компаньонам могут быть заданы собственные имена.