ในรหัส 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")
}
}
สร้างผลลัพธ์:
26 ธันวาคม 2558 11:28:32 น. org.stackoverflow.kotlin.test.MyClass
foo INFO: สวัสดีจาก MyClass
เพิ่มเติมเกี่ยวกับสหายวัตถุที่นี่: Companion วัตถุ ... นอกจากนี้ยังทราบว่าในตัวอย่างข้างต้นMyClass::class.java
ได้รับตัวอย่างของชนิดClass<MyClass>
สำหรับตัดไม้ในขณะที่จะได้รับตัวอย่างของประเภทthis.javaClass
Class<MyClass.Companion>
ตามอินสแตนซ์ของคลาส (ทั่วไป)
แต่ไม่มีเหตุผลจริงๆที่จะหลีกเลี่ยงการโทรและรับตัวบันทึกในระดับอินสแตนซ์ วิธีการใช้สำนวนภาษาจาวาที่คุณกล่าวถึงนั้นล้าสมัยและขึ้นอยู่กับความกลัวของประสิทธิภาพในขณะที่คนตัดไม้ต่อคลาสนั้นถูกแคชไว้แล้วโดยระบบการบันทึกที่เหมาะสมเกือบทุกแห่งในโลก เพียงแค่สร้างสมาชิกเพื่อเก็บวัตถุตัวบันทึก
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
สร้างผลลัพธ์:
26 ธันวาคม 2558 11:28:44 น. org.stackoverflow.kotlin.test.MyClass foo INFO: สวัสดีจาก MyClass
คุณสามารถทดสอบประสิทธิภาพได้ทั้งแบบต่ออินสแตนซ์และแบบต่อคลาสและดูว่ามีความแตกต่างที่เหมือนจริงหรือไม่สำหรับแอพส่วนใหญ่
ตัวแทนอสังหาริมทรัพย์ (ธรรมดาและหรูหราที่สุด)
อีกวิธีหนึ่งซึ่งแนะนำโดย @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"
รหัสนี้ยังตรวจสอบให้แน่ใจว่าถ้าคุณใช้ในวัตถุ Companion ว่าชื่อคนตัดไม้จะเป็นเช่นเดียวกับถ้าคุณใช้มันในชั้นเรียนของตัวเอง ตอนนี้คุณสามารถ:
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()
คลาสทั้งสองนี้จะเป็น:
26 ธันวาคม 2558 11:30:55 น. org.stackoverflow.kotlin.test.Something foo INFO: Hello from Something
26 ธันวาคม 2558 11:30:55 น. org.stackoverflow.kotlin.test.SomethingElse foo INFO: สวัสดีจาก SomethingElse
ฟังก์ชั่นการขยาย (เรื่องแปลกในกรณีนี้เพราะ "มลภาวะ" ของเนมสเปซใด ๆ )
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")
}
}
การผลิตการส่งออก:
26 ธันวาคม 2558 11:29:12 น. org.stackoverflow.kotlin.test.SomethingDifferent foo INFO: สวัสดีจาก 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()
}
การผลิตการส่งออก:
26 ธันวาคม 2558 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo INFO: สวัสดีจาก MarkedClass
หากคุณต้องการบังคับให้สร้างเขตข้อมูลที่เหมือนกันเพื่อเก็บ logger ไว้ในขณะที่ใช้อินเทอร์เฟซนี้คุณอาจต้องการให้ implementer มีฟิลด์เช่น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 เพื่อให้ตรงกับความต้องการของคุณ ทั้งในส่วนขยายหรือฟังก์ชั่นระดับบนสุด นี่คือส่วนประสมเพื่อให้คุณมีตัวเลือกสำหรับวิธีการสร้างตัวบันทึกและตัวอย่างแสดงรูปแบบทั้งหมด:
// 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 ตัวของตัวบันทึกที่สร้างในตัวอย่างนี้จะสร้างชื่อตัวบันทึกและการส่งออกเดียวกัน
26 ธันวาคม 2558 11:39:00 AM org.stackoverflow.kotlin.test.MixedBagOfTricks foo ข้อมูล: สวัสดีจาก MixedBagOfTricks
หมายเหตุ:unwrapCompanionClass()
วิธีการเพื่อให้แน่ใจว่าเราไม่ได้สร้างคนตัดไม้ตั้งชื่อตามวัตถุสหาย แต่ระดับปิดล้อม นี่เป็นวิธีที่แนะนำในปัจจุบันเพื่อค้นหาคลาสที่มีวัตถุที่แสดงร่วม การตัดทอน " $ Companion " จากชื่อที่ใช้removeSuffix()
ไม่ทำงานเนื่องจากออบเจ็กต์ที่แสดงร่วมสามารถให้ชื่อแบบกำหนดเองได้