W większości dojrzałego kodu Kotlin poniżej znajdziesz jeden z tych wzorców. Podejście wykorzystujące delegatów właściwości wykorzystuje moc Kotlin do tworzenia najmniejszego kodu.
Uwaga: kod tutaj jest przeznaczony, java.util.Logging
ale ta sama teoria dotyczy każdej biblioteki rejestrowania
Statyczne (typowe, odpowiednik twojego kodu Java w pytaniu)
Jeśli nie możesz ufać wydajności tego wyszukiwania skrótu w systemie rejestrowania, możesz uzyskać podobne zachowanie do kodu Java, używając obiektu towarzyszącego, który może przechowywać instancję i sprawiać wrażenie statycznego.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
tworzenie wyników:
26 grudnia 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass
foo INFO: Witam z MyClass
Więcej o obiektach towarzyszących tutaj: Obiekty towarzyszące ... Zauważ również, że w powyższym przykładzie MyClass::class.java
pobiera instancję typu Class<MyClass>
dla rejestrującego, podczas gdy this.javaClass
pobiera instancję typu Class<MyClass.Companion>
.
Na wystąpienie klasy (wspólne)
Ale naprawdę nie ma powodu, aby unikać wywoływania i pobierania programu rejestrującego na poziomie instancji. Wspomniany przez ciebie idiomatyczny sposób w Javie jest przestarzały i opiera się na strachu przed wydajnością, podczas gdy rejestrator dla każdej klasy jest już buforowany przez prawie każdy rozsądny system logowania na świecie. Po prostu utwórz członka do przechowywania obiektu rejestrującego.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
tworzenie wyników:
26 grudnia 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Witam z MyClass
Możesz przetestować wydajność zarówno dla instancji, jak i odmian klas, i sprawdzić, czy istnieje realistyczna różnica w przypadku większości aplikacji.
Delegaci nieruchomości (zwykli, najbardziej eleganccy)
Innym podejściem, które sugeruje @Jire w innej odpowiedzi, jest utworzenie delegata właściwości, którego można następnie użyć do jednolitego wykonywania logiki w dowolnej innej klasie, którą chcesz. Jest na to prostszy sposób, ponieważ Kotlin już dostarcza Lazy
delegata, możemy po prostu opakować go w funkcję. Jedna sztuczka polega na tym, że jeśli chcemy poznać typ klasy aktualnie używającej delegata, robimy z tego funkcję rozszerzającą dla dowolnej klasy:
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"
Ten kod zapewnia również, że jeśli użyjesz go w obiekcie towarzyszącym, nazwa programu rejestrującego będzie taka sama, jak w przypadku użycia jej w samej klasie. Teraz możesz po prostu:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
dla instancji klasy lub jeśli chcesz, aby była bardziej statyczna z jedną instancją na klasę:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
Wynik wywołania foo()
obu tych klas byłby następujący:
26 grudnia 2015 11:30:55 org.stackoverflow.kotlin.test.Something foo INFO: Hello from Something
26 grudnia 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Hello from SomethingElse
Funkcje rozszerzające (rzadkie w tym przypadku z powodu „zanieczyszczenia” dowolnej przestrzeni nazw)
Kotlin ma kilka ukrytych sztuczek, które pozwalają jeszcze zmniejszyć część tego kodu. Możesz tworzyć funkcje rozszerzające na klasach i tym samym nadawać im dodatkową funkcjonalność. Jedna sugestia w komentarzach powyżej dotyczyła rozszerzenia Any
o funkcję rejestratora. Może to powodować szum za każdym razem, gdy ktoś używa uzupełniania kodu w swoim IDE w dowolnej klasie. Ale jest sekretna korzyść z rozszerzenia Any
lub innego interfejsu znaczników: możesz zasugerować, że rozszerzasz swoją własną klasę, a tym samym wykryć klasę, w której się znajdujesz. Co? Aby być mniej zagmatwanym, oto kod:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Teraz w klasie (lub obiekcie towarzyszącym) mogę po prostu wywołać to rozszerzenie w mojej własnej klasie:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Produkcja wyników:
26 grudnia 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Hello from SomethingDifferent
Zasadniczo kod jest postrzegany jako wezwanie do rozszerzenia Something.logger()
. Problem polega na tym, że poniższe mogą również być prawdziwe, powodując „zanieczyszczenie” innych klas:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Funkcje rozszerzeń w interfejsie znacznika (nie wiem, jak często, ale wspólny model dla „cech”)
Aby ułatwić korzystanie z rozszerzeń i zmniejszyć „zanieczyszczenie”, możesz użyć interfejsu znaczników, aby rozszerzyć:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Lub nawet uczyń metodę częścią interfejsu z domyślną implementacją:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
I użyj jednej z tych odmian w swojej klasie:
class MarkedClass: Loggable {
val LOG = logger()
}
Produkcja wyników:
26 grudnia 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Witam z MarkedClass
Jeśli chciałbyś wymusić utworzenie jednolitego pola do przechowywania loggera, to korzystając z tego interfejsu możesz łatwo wymagać od realizatora posiadania pola takiego jak LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Teraz implementator interfejsu musi wyglądać tak:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Oczywiście abstrakcyjna klasa bazowa może zrobić to samo, mając opcję zarówno interfejsu, jak i klasy abstrakcyjnej implementującej ten interfejs, co zapewnia elastyczność i jednolitość:
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!
}
}
Kładąc wszystko razem (mała biblioteka pomocnicza)
Oto mała biblioteka pomocnicza, dzięki której każda z powyższych opcji jest łatwa w użyciu. Kotlin często rozszerza API, aby bardziej odpowiadały Twoim upodobaniom. Albo w rozszerzeniu, albo w funkcjach najwyższego poziomu. Oto zestawienie opcji tworzenia rejestratorów oraz próbka pokazująca wszystkie odmiany:
// 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()
}
Wybierz jedną z tych, które chcesz zachować, a oto wszystkie używane opcje:
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")
}
}
Wszystkie 13 wystąpień rejestratorów utworzonych w tym przykładzie będzie generować tę samą nazwę programu rejestrującego i dane wyjściowe:
26 grudnia 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Witam z MixedBagOfTricks
Uwaga: Do unwrapCompanionClass()
metoda gwarantuje, że nie generują rejestratora po nazwie obiektu towarzyszącego ale raczej klasy okalającego. Jest to obecnie zalecany sposób znajdowania klasy zawierającej obiekt towarzyszący. Usunięcie „ $ Companion ” z nazwy za pomocą removeSuffix()
nie działa, ponieważ obiektom towarzyszącym można nadawać niestandardowe nazwy.