У більшості зрілих кодів Котліна ви знайдете одну з цих моделей нижче. Підхід, що використовує власників делегатів, використовує потужність Котліна для створення найменшого коду.
Примітка: код тут є, 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
INFO: Привіт від MyClass
Детальніше про супутні об’єкти тут: Об'єкти супутника ... Також зверніть увагу, що у наведеному вище прикладі MyClass::class.java
отримується екземпляр типу Class<MyClass>
для реєстратора, тоді як this.javaClass
отримав би екземпляр типу Class<MyClass.Companion>
.
По інстанції класу (загальний)
Але насправді немає причин уникати дзвінків та отримання реєстратора на рівні екземпляра. Ідіоматичний спосіб Яви, який ви згадали, застарів і базується на страхуванні продуктивності, тоді як реєстратор кожного класу вже кешований практично будь-якою розумною системою лісозаготівлі на планеті. Просто створіть член для утримання об'єкта реєстратора.
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 в іншій відповіді, полягає у створенні делегата властивості, який ви можете використовувати для того, щоб здійснювати логіку рівномірно в будь-якому іншому класі, який ви хочете. Існує простіший спосіб зробити це, оскільки Котлін вже надає 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"
Цей код також гарантує, що якщо ви використовуєте його в Companion Object, ім'я реєстратора буде таким самим, як якщо б ви використовували його в самому класі. Тепер ви можете просто:
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 INFO: Привіт від чогось
26 грудня 2015 р. 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Привіт від SomethingElse
Функції розширення (рідкість у цьому випадку через "забруднення" будь-якого простору імен)
У Котліна є кілька прихованих хитрощів, які дозволяють зробити деякі з цього коду ще меншими. Ви можете створювати функції розширень на класах і, таким чином, надавати їм додаткову функціональність. Одне з пропозицій у коментарях вище було розширити 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 INFO: Привіт від 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 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO: Привіт від 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!
}
}
Складаємо все разом (невелика бібліотека-помічник)
Ось невелика бібліотека помічників, щоб зробити будь-який із варіантів вище простим у використанні. У Котліні звичайно розширювати 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 INFO: Привіт від MixedBagOfTricks
Примітка . unwrapCompanionClass()
Метод гарантує, що ми не генеруємо реєстратор, названий на честь об'єкта-супутника, а клас, що додає. Це поточний рекомендований спосіб пошуку класу, що містить супровідний об’єкт. Зняття " $ Companion " з імені з використанням імені removeSuffix()
не працює, оскільки супутнім об'єктам можуть бути призначені власні імена.