Di sebagian besar kode Kotlin dewasa, Anda akan menemukan salah satu pola ini di bawah. Pendekatan menggunakan Delegasi Properti mengambil keuntungan dari kekuatan Kotlin untuk menghasilkan kode terkecil.
Catatan: kode di sini adalah untuk java.util.Logging
tetapi teori yang sama berlaku untuk pustaka logging manapun
Seperti statis (umum, setara dengan kode Java Anda dalam pertanyaan)
Jika Anda tidak dapat mempercayai kinerja pencarian hash di dalam sistem logging, Anda bisa mendapatkan perilaku yang mirip dengan kode Java Anda dengan menggunakan objek pendamping yang dapat menyimpan instance dan terasa seperti statis untuk Anda.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
menciptakan output:
26 Des 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass
INFO foo: Halo dari MyClass
Lebih lanjut tentang objek pengiring di sini: Objek Pengiring ... Juga perhatikan bahwa dalam sampel di atas MyClass::class.java
mendapatkan instance tipe Class<MyClass>
untuk logger, sedangkan this.javaClass
akan mendapatkan instance tipe Class<MyClass.Companion>
.
Per Instance of a Class (umum)
Tapi, sebenarnya tidak ada alasan untuk menghindari panggilan dan mendapatkan logger di tingkat instance. Cara Jawa idiomatis yang Anda sebutkan sudah usang dan didasarkan pada rasa takut akan kinerja, sedangkan penebang per kelas sudah di-cache oleh hampir semua sistem logging yang masuk akal di planet ini. Cukup buat anggota untuk memegang objek logger.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
menciptakan output:
26 Des 2015 11:28:44 AM org.stackoverflow.kotlin.test.MyClass foo INFO: Halo dari MyClass
Anda dapat menguji kinerja per variasi instance dan per kelas dan melihat apakah ada perbedaan realistis untuk sebagian besar aplikasi.
Delegasi Properti (umum, paling elegan)
Pendekatan lain, yang disarankan oleh @Jire dalam jawaban lain, adalah membuat delegasi properti, yang kemudian dapat Anda gunakan untuk melakukan logika secara seragam di kelas lain yang Anda inginkan. Ada cara yang lebih sederhana untuk melakukan ini karena Kotlin sudah menyediakan Lazy
delegasi, kita bisa membungkusnya dalam suatu fungsi. Satu trik di sini adalah jika kita ingin mengetahui tipe kelas yang saat ini menggunakan delegate, kita menjadikannya fungsi ekstensi pada kelas apa saja:
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"
Kode ini juga memastikan bahwa jika Anda menggunakannya di objek pendamping, nama logger akan sama seperti jika Anda menggunakannya di kelas itu sendiri. Sekarang Anda cukup:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
untuk per instance kelas, atau jika Anda ingin agar lebih statis dengan satu instance per kelas:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
Dan hasil Anda dari memanggil foo()
kedua kelas ini adalah:
26 Des 2015 11:30:55 AM org.stackoverflow.kotlin.test.Ada sesuatu INFO: Halo dari Sesuatu
26 Des 2015 11:30:55 AM org.stackoverflow.kotlin.test. Sesuatu yang Benar INFO: Halo dari SomethingElse
Fungsi Ekstensi (tidak umum dalam kasus ini karena "polusi" dari namespace Apa saja)
Kotlin memiliki beberapa trik tersembunyi yang memungkinkan Anda membuat beberapa kode ini menjadi lebih kecil. Anda dapat membuat fungsi ekstensi di kelas dan karenanya memberi mereka fungsi tambahan. Satu saran dalam komentar di atas adalah untuk memperluas Any
dengan fungsi logger. Ini dapat membuat noise kapan saja seseorang menggunakan penyelesaian kode dalam IDE mereka di kelas mana pun. Tetapi ada manfaat rahasia untuk memperluas Any
atau antarmuka penanda lain: Anda dapat menyiratkan bahwa Anda memperluas kelas Anda sendiri dan karenanya mendeteksi kelas Anda berada di dalam. Hah? Agar tidak membingungkan, berikut ini kodenya:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Sekarang di dalam kelas (atau objek pendamping), saya cukup memanggil ekstensi ini di kelas saya sendiri:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Memproduksi output:
26 Des 2015 11:29:12 pagi org.stackoverflow.kotlin.test. Sesuatu yang Berbeda INFO: Halo dari SomethingDifferent
Pada dasarnya, kode ini dilihat sebagai panggilan ke ekstensi Something.logger()
. Masalahnya adalah bahwa hal-hal berikut ini juga bisa benar menciptakan "polusi" pada kelas lain:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Fungsi Ekstensi pada Antarmuka Penanda (tidak yakin seberapa umum, tetapi model umum untuk "ciri")
Untuk membuat penggunaan ekstensi lebih bersih dan mengurangi "polusi", Anda dapat menggunakan antarmuka penanda untuk memperluas:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Atau bahkan menjadikan metode bagian dari antarmuka dengan implementasi default:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Dan gunakan salah satu dari variasi ini di kelas Anda:
class MarkedClass: Loggable {
val LOG = logger()
}
Memproduksi output:
26 Des 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO: Halo dari MarkedClass
Jika Anda ingin memaksa pembuatan bidang yang seragam untuk menahan logger, maka saat menggunakan antarmuka ini Anda dapat dengan mudah meminta pelaksana memiliki bidang seperti LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Sekarang pelaksana antarmuka harus terlihat seperti ini:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Tentu saja, kelas dasar abstrak dapat melakukan hal yang sama, memiliki opsi antarmuka dan kelas abstrak yang mengimplementasikan antarmuka memungkinkan fleksibilitas dan keseragaman:
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!
}
}
Putting It All Together (Perpustakaan pembantu kecil)
Berikut adalah perpustakaan pembantu kecil untuk membuat salah satu opsi di atas mudah digunakan. Adalah umum di Kotlin untuk memperpanjang API agar lebih sesuai dengan keinginan Anda. Baik dalam fungsi ekstensi atau tingkat atas. Berikut adalah campuran untuk memberi Anda opsi cara membuat logger, dan sampel yang menunjukkan semua variasi:
// 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()
}
Pilih yang mana yang ingin Anda simpan, dan berikut ini semua opsi yang digunakan:
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")
}
}
Ke-13 instance dari logger yang dibuat dalam sampel ini akan menghasilkan nama logger yang sama, dan output:
26 Des 2015 11:39:00 pagi org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Halo dari MixedBagOfTricks
Catatan: The unwrapCompanionClass()
Metode memastikan bahwa kita tidak menghasilkan logger dinamai objek pendamping melainkan kelas melampirkan. Ini adalah cara yang disarankan saat ini untuk menemukan kelas yang berisi objek pendamping. Menghapus " $ Companion " dari penggunaan nama removeSuffix()
tidak berfungsi karena objek pendamping dapat diberikan nama khusus.