成熟したKotlinコードの大部分では、これらのパターンの1つを以下に示します。プロパティデリゲートを使用するアプローチは、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")
}
}
出力の作成:
2015年12月26日11:28:32 AM org.stackoverflow.kotlin.test.MyClass
foo INFO:MyClassからこんにちは
ここでコンパニオンオブジェクトの詳細:コンパニオンオブジェクト ...また、上記のサンプルでは、ロガーのタイプのインスタンスを取得するのに対し、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")
}
}
出力の作成:
2015年12月26日11:28:44 org.stackoverflow.kotlin.test.MyClass foo INFO:Hello from MyClass
インスタンスごととクラスごとのバリエーションの両方でパフォーマンステストを実行し、ほとんどのアプリに現実的な違いがあるかどうかを確認できます。
プロパティデリゲート(一般的、最もエレガント)
@Jireが別の回答で提案している別のアプローチは、プロパティデリゲートを作成することです。これを使用して、必要な他のクラスでロジックを均一に実行できます。KotlinはLazy
既にデリゲートを提供しているため、これを行う簡単な方法があります。これを関数にラップするだけです。ここでの1つのトリックは、現在デリゲートを使用しているクラスのタイプを知りたい場合、それを任意のクラスの拡張関数にすることです。
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")
}
}
クラスインスタンスごと、またはクラスごとに1つのインスタンスを使用してより静的にしたい場合:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
そして、foo()
これらのクラスの両方を呼び出した結果は次のようになります。
2015年12月26日11:30:55 org.stackoverflow.kotlin.test.Something foo INFO:Hello from Something
2015年12月26日11:30:55 org.stackoverflow.kotlin.test.SomethingElse foo INFO:Hello from SomethingElse
拡張関数(名前空間の「汚染」のため、この場合は一般的ではありません)
Kotlinにはいくつかの隠されたトリックがあり、このコードの一部をさらに小さくすることができます。クラスに拡張関数を作成して、追加の機能を与えることができます。上記のコメントの1つの提案は、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")
}
}
出力の生成:
2015年12月26日11:29:12 org.stackoverflow.kotlin.test.SomethingDifferent foo INFO:Hello from SomethingDifferent
基本的に、コードはextensionの呼び出しと見なされ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()
}
出力の生成:
2015年12月26日11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO:Hello from 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を拡張して好みに合わせて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個のインスタンスはすべて、同じロガー名と出力を生成します。
2015年12月26日11:39:00 org.stackoverflow.kotlin.test.MixedBagOfTricks foo INFO:Hello from MixedBagOfTricks
注: このunwrapCompanionClass()
メソッドにより、コンパニオンオブジェクトにちなんで名付けられたロガーではなく、それを囲むクラスが生成されることが保証されます。これは、コンパニオンオブジェクトを含むクラスを検索する現在の推奨方法です。コンパニオンオブジェクトにはカスタム名を付けることができるため、名前を使用して「$ Companion」をremoveSuffix()
削除しても機能しません。