Trong phần lớn mã Kotlin trưởng thành, bạn sẽ tìm thấy một trong những mẫu dưới đây. Cách tiếp cận sử dụng Thuộc tính sở hữu tận dụng sức mạnh của Kotlin để tạo ra mã nhỏ nhất.
Lưu ý: mã ở đây là dành cho java.util.Logging
nhưng lý thuyết tương tự áp dụng cho bất kỳ thư viện ghi nhật ký nào
Giống như tĩnh (phổ biến, tương đương với mã Java của bạn trong câu hỏi)
Nếu bạn không thể tin tưởng vào hiệu suất của tra cứu băm đó bên trong hệ thống ghi nhật ký, bạn có thể có hành vi tương tự với mã Java của mình bằng cách sử dụng một đối tượng đồng hành có thể giữ một cá thể và cảm thấy như một động tĩnh đối với bạn.
class MyClass {
companion object {
val LOG = Logger.getLogger(MyClass::class.java.name)
}
fun foo() {
LOG.warning("Hello from MyClass")
}
}
tạo đầu ra:
Ngày 26 tháng 12 năm 2015 11:28:32 AM org.stackoverflow.kotlin.test.MyClass
foo THÔNG TIN: Xin chào từ MyClass
Thông tin thêm về các đối tượng đồng hành ở đây: Đối tượng đồng hành ... Cũng lưu ý rằng trong mẫu ở trên MyClass::class.java
có thể hiện loại Class<MyClass>
cho trình ghi, trong khi đó this.javaClass
sẽ có thể hiện của loại Class<MyClass.Companion>
.
Mỗi trường hợp của một lớp (phổ biến)
Nhưng, thực sự không có lý do gì để tránh gọi và nhận logger ở cấp độ cá thể. Cách thức thành ngữ Java mà bạn đề cập đã lỗi thời và dựa trên nỗi sợ hiệu năng, trong khi đó trình ghi nhật ký cho mỗi lớp đã được lưu trữ bởi hầu hết mọi hệ thống ghi nhật ký hợp lý trên hành tinh. Chỉ cần tạo một thành viên để giữ đối tượng logger.
class MyClass {
val LOG = Logger.getLogger(this.javaClass.name)
fun foo() {
LOG.warning("Hello from MyClass")
}
}
tạo đầu ra:
Ngày 26 tháng 12 năm 2015 11:28:44 AM org.stackoverflow.kotlin.test.MyClass foo THÔNG TIN: Xin chào từ MyClass
Bạn có thể kiểm tra hiệu suất cả hai biến thể cho mỗi phiên bản và mỗi lớp và xem liệu có sự khác biệt thực tế cho hầu hết các ứng dụng hay không.
Đại biểu tài sản (phổ biến, thanh lịch nhất)
Một cách tiếp cận khác, được đề xuất bởi @Jire trong một câu trả lời khác, là tạo một đại biểu thuộc tính, sau đó bạn có thể sử dụng để thực hiện logic thống nhất trong bất kỳ lớp nào khác mà bạn muốn. Có một cách đơn giản hơn để làm điều này vì Kotlin đã cung cấp một Lazy
đại biểu rồi, chúng ta có thể chỉ cần bọc nó trong một hàm. Một mẹo ở đây là nếu chúng ta muốn biết loại lớp hiện đang sử dụng đại biểu, chúng ta biến nó thành một hàm mở rộng trên bất kỳ lớp nào:
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"
Mã này cũng đảm bảo rằng nếu bạn sử dụng nó trong Đối tượng đồng hành thì tên logger sẽ giống như khi bạn sử dụng nó trên chính lớp đó. Bây giờ bạn có thể chỉ cần:
class Something {
val LOG by logger()
fun foo() {
LOG.info("Hello from Something")
}
}
cho mỗi thể hiện của lớp hoặc nếu bạn muốn nó tĩnh hơn với một thể hiện trên mỗi lớp:
class SomethingElse {
companion object {
val LOG by logger()
}
fun foo() {
LOG.info("Hello from SomethingElse")
}
}
Và đầu ra của bạn từ việc gọi foo()
cả hai lớp này sẽ là:
Ngày 26 tháng 12 năm 2015 11:30:55 AM org.stackoverflow.kotlin.test.S Something foo INFO: Xin chào từ Something
Ngày 26 tháng 12 năm 2015 11:30:55 AM org.stackoverflow.kotlin.test.S SomethingElse foo THÔNG TIN: Xin chào từ SomethingElse
Hàm mở rộng (không phổ biến trong trường hợp này vì "ô nhiễm" của bất kỳ không gian tên nào)
Kotlin có một vài thủ thuật ẩn cho phép bạn tạo một số mã này thậm chí còn nhỏ hơn. Bạn có thể tạo các hàm mở rộng trên các lớp và do đó cung cấp cho chúng chức năng bổ sung. Một gợi ý trong các ý kiến trên là mở rộng Any
với chức năng logger. Điều này có thể tạo ra tiếng ồn bất cứ lúc nào ai đó sử dụng hoàn thành mã trong IDE của họ trong bất kỳ lớp nào. Nhưng có một lợi ích bí mật đối với việc mở rộng Any
hoặc một số giao diện đánh dấu khác: bạn có thể ngụ ý rằng bạn đang mở rộng lớp của riêng mình và do đó phát hiện lớp bạn đang ở trong đó. Huh? Để ít gây nhầm lẫn, đây là mã:
// extend any class with the ability to get a logger
fun <T: Any> T.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Bây giờ trong một lớp (hoặc đối tượng đồng hành), tôi chỉ có thể gọi phần mở rộng này trên lớp của riêng mình:
class SomethingDifferent {
val LOG = logger()
fun foo() {
LOG.info("Hello from SomethingDifferent")
}
}
Sản lượng sản xuất:
Ngày 26 tháng 12 năm 2015 11:29:12 AM org.stackoverflow.kotlin.test.S SomethingDifferent foo THÔNG TIN: Xin chào từ SomethingDifferent
Về cơ bản, mã được xem như một lời kêu gọi gia hạn Something.logger()
. Vấn đề là những điều sau đây cũng có thể đúng là tạo ra "ô nhiễm" trên các lớp khác:
val LOG1 = "".logger()
val LOG2 = Date().logger()
val LOG3 = 123.logger()
Các chức năng mở rộng trên Giao diện đánh dấu (không chắc mức độ phổ biến, nhưng mô hình chung cho "đặc điểm")
Để sử dụng tiện ích mở rộng sạch hơn và giảm "ô nhiễm", bạn có thể sử dụng giao diện đánh dấu để mở rộng:
interface Loggable {}
fun Loggable.logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
Hoặc thậm chí làm cho phần phương thức của giao diện với cài đặt mặc định:
interface Loggable {
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Và sử dụng một trong những biến thể này trong lớp của bạn:
class MarkedClass: Loggable {
val LOG = logger()
}
Sản lượng sản xuất:
Ngày 26 tháng 12 năm 2015 11:41:01 AM org.stackoverflow.kotlin.test.MarkedClass foo THÔNG TIN: Xin chào từ MarkedClass
Nếu bạn muốn buộc tạo một trường thống nhất để giữ bộ ghi, thì trong khi sử dụng giao diện này, bạn có thể dễ dàng yêu cầu người thực hiện phải có một trường như LOG
:
interface Loggable {
val LOG: Logger // abstract required field
public fun logger(): Logger {
return Logger.getLogger(unwrapCompanionClass(this.javaClass).name)
}
}
Bây giờ người thực hiện giao diện phải trông như thế này:
class MarkedClass: Loggable {
override val LOG: Logger = logger()
}
Tất nhiên, một lớp cơ sở trừu tượng có thể làm tương tự, có tùy chọn cả giao diện và lớp trừu tượng thực hiện giao diện đó cho phép linh hoạt và đồng nhất:
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ết hợp tất cả lại với nhau (Một thư viện trợ giúp nhỏ)
Dưới đây là một thư viện trợ giúp nhỏ để làm cho bất kỳ tùy chọn nào ở trên dễ sử dụng. Điều phổ biến ở Kotlin là mở rộng API để làm cho chúng phù hợp hơn với sở thích của bạn. Hoặc trong các chức năng mở rộng hoặc cấp cao nhất. Dưới đây là một kết hợp để cung cấp cho bạn các tùy chọn về cách tạo logger và một mẫu hiển thị tất cả các biến thể:
// 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()
}
Chọn bất kỳ tùy chọn nào bạn muốn giữ và đây là tất cả các tùy chọn đang sử dụng:
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")
}
}
Tất cả 13 phiên bản của logger được tạo trong mẫu này sẽ tạo ra cùng một tên logger và đầu ra:
Ngày 26 tháng 12 năm 2015 11:39:00 AM org.stackoverflow.kotlin.test.MixedBagOfTricks foo THÔNG TIN: Xin chào từ MixedBagOfTricks
Lưu ý: Các unwrapCompanionClass()
chắc chắn method mà chúng ta không tạo ra một logger đặt theo tên của đối tượng bạn đồng hành nhưng thay vì lớp kèm theo. Đây là cách được đề xuất hiện tại để tìm lớp chứa đối tượng đồng hành. Tước " $ Đồng hành " khỏi tên bằng cách sử dụng removeSuffix()
không hoạt động vì các đối tượng đồng hành có thể được đặt tên tùy chỉnh.