성숙한 Kotlin 코드의 대부분에서 다음 패턴 중 하나를 찾을 수 있습니다. Property Delegates를 사용하는 접근 방식 은 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 초 org.stackoverflow.kotlin.test.MyClass
foo 정보 : MyClass의 Hello
동반자 객체에 대한 자세한 내용은 여기를 참조하십시오. Companion Objects ... 또한 위의 샘플 에서는 로거 의 유형 인스턴스를 얻는 반면 MyClass::class.java
type의 인스턴스를 얻는다는 점에 유의하십시오 .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 : MyClass의 Hello
인스턴스 별 및 클래스 별 변형을 모두 성능 테스트하고 대부분의 앱에 현실적인 차이가 있는지 확인할 수 있습니다.
부동산 대표 (일반적이고 가장 우아함)
@Jire가 다른 답변으로 제안하는 또 다른 방법은 속성 대리자를 만드는 것입니다. 그런 다음 원하는 다른 클래스에서 균일하게 논리를 수행하는 데 사용할 수 있습니다. Kotlin은 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"
또한이 코드는 컴패니언 객체에서 로거 이름이 클래스 자체에서 사용한 것처럼 로거 이름을 사용하도록합니다. 이제 간단하게 할 수 있습니다 :
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()
두 클래스를 모두 호출 한 결과는 다음과 같습니다.
2015 년 12 월 26 일 오전 11:30:55 org.stackoverflow.kotlin.test. 무언가 foo 정보 : Hello from Something
2015 년 12 월 26 일 오전 11시 30 분 55 초 org.stackoverflow.kotlin.test.SomethingElse foo INFO : 안녕하세요
확장 함수 (이 경우 네임 스페이스의 "오염"으로 인해 드문 경우)
Kotlin에는이 코드 중 일부를 더 작게 만들 수있는 몇 가지 숨겨진 트릭이 있습니다. 클래스에서 확장 함수를 작성하여 추가 기능을 제공 할 수 있습니다. 위의 의견에서 한 가지 제안은 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
기본적으로 코드는 확장에 대한 호출로 표시됩니다 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 초 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!
}
}
종합 (작은 도우미 라이브러리)
다음은 위의 옵션을 쉽게 사용할 수 있도록 도와주는 작은 도우미 라이브러리입니다. 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 정보 : MixedBagOfTricks의 Hello
참고 : 이 unwrapCompanionClass()
메소드는 컴패니언 객체의 이름을 따서 로거를 생성하지 않고 대신에 클래스를 생성하도록합니다. 컴패니언 객체가 포함 된 클래스를 찾는 데 현재 권장되는 방법입니다. 컴패니언 개체에 사용자 지정 이름을 지정할 수 있으므로 이름에서 " $ Companion "을 제거하면 removeSuffix()
작동하지 않습니다.