In den meisten ausgereiften Kotlin-Codes finden Sie unten eines dieser Muster. Der Ansatz mit Property Delegates nutzt die Leistungsfähigkeit von Kotlin, um den kleinsten Code zu erstellen.
Hinweis: Der Code hier ist für, java.util.Logging
aber die gleiche Theorie gilt für jede Protokollierungsbibliothek
Statisch (häufig, entspricht Ihrem Java-Code in der Frage)
Wenn Sie der Leistung dieser Hash-Suche im Protokollierungssystem nicht vertrauen können, können Sie ein ähnliches Verhalten wie bei Ihrem Java-Code erzielen, indem Sie ein Begleitobjekt verwenden, das eine Instanz enthalten und sich für Sie statisch anfühlen kann.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
Ausgabe erstellen:
26. Dezember 2015 11:28:32 org.stackoverflow.kotlin.test.MyClass
foo INFO: Hallo von MyClass
Mehr über Begleiter Objekte hier: Companion Objekte ... Auch , dass in der Probe beachten Sie über MyClass::class.java
die Instanz des Typs wird Class<MyClass>
für den Logger, während this.javaClass
die Instanz des Typs erhalten würde Class<MyClass.Companion>
.
Pro Instanz einer Klasse (gemeinsam)
Es gibt jedoch keinen Grund, das Aufrufen und Abrufen eines Loggers auf Instanzebene zu vermeiden. Die von Ihnen erwähnte idiomatische Java-Methode ist veraltet und basiert auf Leistungsangst, während der Logger pro Klasse bereits von fast jedem vernünftigen Protokollierungssystem auf dem Planeten zwischengespeichert wird. Erstellen Sie einfach ein Mitglied, das das Logger-Objekt enthält.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
Ausgabe erstellen:
26. Dezember 2015 11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO: Hallo von MyClass
Sie können Leistungstests sowohl pro Instanz als auch pro Klasse durchführen und feststellen, ob für die meisten Apps ein realistischer Unterschied besteht.
Property Delegates (gewöhnlich, am elegantesten)
Ein anderer Ansatz, der von @Jire in einer anderen Antwort vorgeschlagen wird, besteht darin, einen Eigenschaftendelegaten zu erstellen, mit dem Sie die Logik in jeder anderen gewünschten Klasse einheitlich ausführen können. Es gibt einen einfacheren Weg, dies zu tun, da Kotlin Lazy
bereits einen Delegaten bereitstellt . Wir können ihn einfach in eine Funktion einschließen. Ein Trick dabei ist, dass wir, wenn wir den Typ der Klasse kennen möchten, die derzeit den Delegaten verwendet, ihn zu einer Erweiterungsfunktion für jede Klasse machen:
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"
Dieser Code stellt außerdem sicher, dass bei Verwendung in einem Companion-Objekt der Loggername mit dem Namen übereinstimmt, den Sie für die Klasse selbst verwendet haben. Jetzt können Sie einfach:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
für pro Klasseninstanz oder wenn Sie möchten, dass es mit einer Instanz pro Klasse statischer ist:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
Und Ihre Ausgabe vom Aufrufen dieser foo()
beiden Klassen wäre:
26. Dezember 2015 11:30:55 org.stackoverflow.kotlin.test.Something foo INFO: Hallo von etwas
26. Dezember 2015 11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO: Hallo von SomethingElse
Erweiterungsfunktionen (in diesem Fall aufgrund der "Verschmutzung" eines beliebigen Namespace ungewöhnlich)
Kotlin hat ein paar versteckte Tricks, mit denen Sie einen Teil dieses Codes noch kleiner machen können. Sie können Erweiterungsfunktionen für Klassen erstellen und ihnen daher zusätzliche Funktionen geben. Ein Vorschlag in den obigen Kommentaren war, Any
mit einer Logger-Funktion zu erweitern. Dies kann jedes Mal zu Rauschen führen, wenn jemand in einer ID eine Code-Vervollständigung in seiner IDE verwendet. Das Erweitern Any
oder eine andere Markierungsschnittstelle hat jedoch einen geheimen Vorteil : Sie können implizieren, dass Sie Ihre eigene Klasse erweitern und somit die Klasse erkennen, in der Sie sich befinden. Huh? Um weniger verwirrend zu sein, hier ist der Code:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Jetzt kann ich innerhalb einer Klasse (oder eines Begleitobjekts) diese Erweiterung einfach für meine eigene Klasse aufrufen:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Ausgabe produzieren:
26. Dezember 2015 11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: Hallo von SomethingDifferent
Grundsätzlich wird der Code als Aufruf zur Erweiterung angesehen Something.logger()
. Das Problem ist, dass Folgendes auch zutreffen könnte, was zu einer "Verschmutzung" anderer Klassen führt:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Erweiterungsfunktionen auf der Marker-Schnittstelle (nicht sicher, wie häufig, aber gemeinsames Modell für "Merkmale")
Um die Verwendung von Erweiterungen sauberer zu gestalten und die "Verschmutzung" zu verringern, können Sie eine Markierungsschnittstelle verwenden, um Folgendes zu erweitern:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Oder machen Sie die Methode mit einer Standardimplementierung zu einem Teil der Schnittstelle:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Und verwenden Sie eine dieser Variationen in Ihrer Klasse:
class MarkedClass: Loggable {
val LOG = logger()
}
Ausgabe produzieren:
26. Dezember 2015 11:41:01 org.stackoverflow.kotlin.test.MarkedClass foo INFO: Hallo von MarkedClass
Wenn Sie die Schaffung eines einheitlichen Feldes zwingen wollte , den Logger zu halten, dann , während Sie diese Schnittstelle können Sie bequem die Implementierer erfordern ein Feld zu haben , wie zum Beispiel LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Jetzt muss der Implementierer der Schnittstelle folgendermaßen aussehen:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Natürlich kann eine abstrakte Basisklasse dasselbe tun, wobei die Option, dass sowohl die Schnittstelle als auch eine abstrakte Klasse, die diese Schnittstelle implementiert, Flexibilität und Einheitlichkeit ermöglichen:
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!
}
}
Alles zusammenfügen (Eine kleine Hilfsbibliothek)
Hier ist eine kleine Hilfsbibliothek, mit der Sie die oben genannten Optionen einfach verwenden können. In Kotlin ist es üblich, APIs zu erweitern, um sie Ihren Wünschen anzupassen. Entweder in Erweiterungs- oder Top-Level-Funktionen. Hier finden Sie eine Mischung mit Optionen zum Erstellen von Loggern sowie ein Beispiel mit allen Variationen:
// 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()
}
Wählen Sie die Optionen aus, die Sie behalten möchten, und hier sind alle verwendeten Optionen aufgeführt:
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")
}
}
Alle 13 Instanzen der in diesem Beispiel erstellten Logger erzeugen denselben Loggernamen und dieselbe Ausgabe:
26. Dezember 2015 11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO: Hallo von MixedBagOfTricks
Hinweis: Die unwrapCompanionClass()
Methode stellt sicher, dass kein Logger generiert wird, der nach dem Begleitobjekt benannt ist, sondern nach der einschließenden Klasse. Dies ist die derzeit empfohlene Methode, um die Klasse zu finden, die das Begleitobjekt enthält. Das Entfernen von " $ Companion " aus dem Namen mit removeSuffix()
funktioniert nicht, da Companion-Objekte benutzerdefinierte Namen erhalten können.