如何在 Kotlin 中以惯用方式编写一个 Java 函数式接口的默认方法?

huangapple go评论79阅读模式
英文:

How to write Java's default method of a functional interface in Kotlin idiomatically?

问题

以下是你提供的内容的翻译部分:

Java 代码

这是正确工作的 Chain of Responsibility 设计模式的 Java 代码:

import java.util.Arrays;
import java.util.EnumSet;
import java.util.function.Consumer;

@FunctionalInterface
public interface Logger {
    public enum LogLevel {
        INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;

        public static LogLevel[] all() {
            return values();
        }
    }

    abstract void message(String msg, LogLevel severity);

    default Logger appendNext(Logger nextLogger) {
        return (msg, severity) -> {
            message(msg, severity);
            nextLogger.message(msg, severity);
        };
    }

    // ... 其他静态工厂方法 ...

    public static void main(String[] args) {
        // 构建一个不可变的责任链
        Logger logger = consoleLogger(LogLevel.all())
                .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
                .appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR));

        // 处理由 consoleLogger 处理,因为控制台具有 LogLevel.all()
        logger.message("Entering function ProcessOrder().", LogLevel.DEBUG);
        logger.message("Order record retrieved.", LogLevel.INFO);

        // 由 consoleLogger 和 emailLogger 处理,因为 emailLogger 实现了 Functional_Error 和 Functional_Message
        logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR);
        logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE);

        // 由 consoleLogger 和 fileLogger 处理,因为 fileLogger 实现了 Warning 和 Error
        logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING);
        logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR);
    }
}

Kotlin 代码

这是我迄今为止尝试的 Kotlin 代码。我将 Enum 移动到了单独的文件,并将所有内容保持在顶层。请看 appendNext() 方法,这似乎是问题的原因。

Logger.kt

import java.util.*
import java.util.function.Consumer

interface Logger {
    fun message(message: String, severity: LogLevel)
}

fun Logger.appendNext(nextLogger: Logger): Logger {
    return object : Logger {
        override fun message(message: String, severity: LogLevel) {
            message(message, severity)
            nextLogger.message(message, severity)
        }
    }
}

// ... 其他静态工厂方法 ...

fun main() {
    // 构建一个不可变的责任链
    val logger = consoleLogger(*LogLevel.all())
        .appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
        .appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR))

    // 处理由 consoleLogger 处理,因为控制台具有 LogLevel.all()
    logger.message("Entering function ProcessOrder().", LogLevel.DEBUG)
    logger.message("Order record retrieved.", LogLevel.INFO)

    // 由 consoleLogger 和 emailLogger 处理,因为 emailLogger 实现了 Functional_Error 和 Functional_Message
    logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR)
    logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE)

    // 由 consoleLogger 和 fileLogger 处理,因为 fileLogger 实现了 Warning 和 Error
    logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING)
    logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR)
}

LogLevel.kt

enum class LogLevel {
    INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;

    companion object {
        fun all(): Array<LogLevel> {
            return values()
        }
    }
}
英文:

I'm trying to convert the Java code of a design pattern called Chain of Responsibility in Kotlin idiomatically. But I'm not getting any clue on converting the default method appendNext() of Java interface in Kotlin. I tried some already existing questions like this and this but they don't seem to be working for my use case.

I tried converting the default method appendNext() to an extension function in Kotlin. But apparently Kotlin doesn't seem to find the method Logger.message() and throws NoSuchMethodError.

I have given the original Java code and the Kotlin code I tried so far in the following snippets.

I would prefer a Kotlin idiomatic solution of this code without using the @JvmDefault annotation. The code should be as concise as Java if not more. Any help would be much appreciated.

Java code

This is the correctly working Java code for the design pattern Chain of Responsibility:

import java.util.Arrays;
import java.util.EnumSet;
import java.util.function.Consumer;
@FunctionalInterface
public interface Logger {
public enum LogLevel {
INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;
public static LogLevel[] all() {
return values();
}
}
abstract void message(String msg, LogLevel severity);
default Logger appendNext(Logger nextLogger) {
return (msg, severity) -&gt; {
message(msg, severity);
nextLogger.message(msg, severity);
};
}
static Logger writeLogger(LogLevel[] levels, Consumer&lt;String&gt; stringConsumer) {
EnumSet&lt;LogLevel&gt; set = EnumSet.copyOf(Arrays.asList(levels));
return (msg, severity) -&gt; {
if (set.contains(severity)) {
stringConsumer.accept(msg);
}
};
}
static Logger consoleLogger(LogLevel... levels) {
return writeLogger(levels, msg -&gt; System.err.println(&quot;Writing to console: &quot; + msg));
}
static Logger emailLogger(LogLevel... levels) {
return writeLogger(levels, msg -&gt; System.err.println(&quot;Sending via email: &quot; + msg));
}
static Logger fileLogger(LogLevel... levels) {
return writeLogger(levels, msg -&gt; System.err.println(&quot;Writing to Log File: &quot; + msg));
}
public static void main(String[] args) {
// Build an immutable chain of responsibility
Logger logger = consoleLogger(LogLevel.all())
.appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
.appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR));
// Handled by consoleLogger since the console has a LogLevel of all
logger.message(&quot;Entering function ProcessOrder().&quot;, LogLevel.DEBUG);
logger.message(&quot;Order record retrieved.&quot;, LogLevel.INFO);
// Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error &amp; Functional_Message
logger.message(&quot;Unable to Process Order ORD1 Dated D1 For Customer C1.&quot;, LogLevel.FUNCTIONAL_ERROR);
logger.message(&quot;Order Dispatched.&quot;, LogLevel.FUNCTIONAL_MESSAGE);
// Handled by consoleLogger and fileLogger since fileLogger implements Warning &amp; Error
logger.message(&quot;Customer Address details missing in Branch DataBase.&quot;, LogLevel.WARNING);
logger.message(&quot;Customer Address details missing in Organization DataBase.&quot;, LogLevel.ERROR);
}
}

Kotlin code

This is what I tried so far. I moved the Enum to a separate file and kept everything at top level. Have a look at the appendNext() method, this is what seems to be the cause of the issue.

Logger.kt

import java.util.*
import java.util.function.Consumer
interface Logger {
fun message(message: String, severity: LogLevel)
}
fun Logger.appendNext(nextLogger: Logger): Logger {
return object: Logger {
override fun message(message: String, severity: LogLevel) {
message(message, severity)
nextLogger.message(message, severity)
}
}
}
fun writeLogger(
stringConsumer: Consumer&lt;String&gt;,
vararg levels: LogLevel
): Logger {
val set = EnumSet.copyOf(listOf(*levels))
return object: Logger {
override fun message(message: String, severity: LogLevel) {
if (set.contains(severity)) {
stringConsumer.accept(message)
}
}
}
}
fun consoleLogger(vararg levels: LogLevel): Logger {
return writeLogger(
Consumer { msg: String -&gt; System.err.println(&quot;Writing to console: $msg&quot;) },
*levels
)
}
fun emailLogger(vararg levels: LogLevel): Logger {
return writeLogger(
Consumer { msg: String -&gt; System.err.println(&quot;Sending via email: $msg&quot;) },
*levels
)
}
fun fileLogger(vararg levels: LogLevel): Logger {
return writeLogger(
Consumer { msg: String -&gt; System.err.println(&quot;Writing to Log File: $msg&quot;) },
*levels
)
}
fun main() {
// Build an immutable chain of responsibility
val logger = consoleLogger(*LogLevel.all())
.appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
.appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR))
// Handled by consoleLogger since the console has a LogLevel of all
logger.message(&quot;Entering function ProcessOrder().&quot;, LogLevel.DEBUG)
logger.message(&quot;Order record retrieved.&quot;, LogLevel.INFO)
// Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error &amp; Functional_Message
logger.message(&quot;Unable to Process Order ORD1 Dated D1 For Customer C1.&quot;, LogLevel.FUNCTIONAL_ERROR)
logger.message(&quot;Order Dispatched.&quot;, LogLevel.FUNCTIONAL_MESSAGE)
// Handled by consoleLogger and fileLogger since fileLogger implements Warning &amp; Error
logger.message(&quot;Customer Address details missing in Branch DataBase.&quot;, LogLevel.WARNING)
logger.message(&quot;Customer Address details missing in Organization DataBase.&quot;, LogLevel.ERROR)
}

LogLevel.kt

enum class LogLevel {
INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;
companion object {
public fun all(): Array&lt;LogLevel&gt; {
return values()
}
}
}

答案1

得分: 1

我不明白为什么你添加了一个在原始Java代码中不存在的currentLogger属性。

如果你想要与Java中相同的行为,其中实现可以覆盖appendNext()的默认实现,代码如下:

fun interface Logger {
    fun message(message: String, severity: LogLevel)

    fun appendNext(nextLogger: Logger): Logger {
        return Logger { message, severity ->
            message(message, severity)
            nextLogger.message(message, severity)
        }
    }
}

如果你不打算让这个函数被覆盖,将它转为扩展函数会更合适。然后,“覆盖”它将需要使用具有相同签名的另一个扩展函数来进行组合,并导入那个扩展函数来使用。这就是标准库函数的组织方式。尽管不是绝对安全的,但将函数放在接口中会更强烈地暗示它是可以被覆盖的。

fun interface Logger {
    fun message(message: String, severity: LogLevel)
}

fun Logger.appendNext(nextLogger: Logger): Logger {
    return Logger { message, severity ->
        message(message, severity)
        nextLogger.message(message, severity)
    }
}

另外,你不应该需要使用Consumer,因为在Kotlin中函数是一等类型。例如,用(String) -> Unit替换Consumer<String>,然后可以直接使用stringConsumer(message)来调用它,而不是stringConsumer.accept(message)

英文:

I don't see why you've added a currentLogger property that didn't exist in the original Java code.

If you want the same behavior as in Java, where an implementation can override the default implementation of appendNext(), it would look like this:

fun interface Logger {
fun message(message: String, severity: LogLevel)
fun appendNext(nextLogger: Logger): Logger {
return Logger { message, severity -&gt;
message(message, severity)
nextLogger.message(message, severity)
}
}
}

If you don't intend for this function to be overridden, it would be more suitable to move it to an extension function. Then "overriding" it would require composing another extension function with the same signature and importing that one instead to use it. This is how the standard library functions are organized. Still not foolproof, but putting the function in the interface would more strongly suggest that it is meant to be overridden.

fun interface Logger {
fun message(message: String, severity: LogLevel)
}
fun Logger.appendNext(nextLogger: Logger): Logger {
return Logger { message, severity -&gt;
message(message, severity)
nextLogger.message(message, severity)
}
}

Edit: Also, you should not need to use Consumer, since in Kotlin functions are first class types. For example, replace Consumer&lt;String&gt; with (String) -&gt; Unit and then call it directly with stringConsumer(message) instead of stringConsumer.accept(message).

答案2

得分: 1

以下是翻译好的部分:

关键在于使用函数式接口(在Kotlin 1.4中引入)的用法。

此外,为了使代码更加简洁和符合惯例,考虑使用单表达式函数,移除可以推断的类型,并将EnumSet.copyOf(listOf(*levels))重写为辅助函数,避免额外的对象创建:

import java.util.*
import java.util.function.Consumer
fun interface Logger {
fun message(msg: String, severity: LogLevel)
}
fun Logger.appendNext(nextLogger: Logger) = Logger { msg, severity ->
message(msg, severity)
nextLogger.message(msg, severity)
}
inline fun <reified E : Enum<E>> enumSetOf(e: Array<out E>): EnumSet<E> =
EnumSet.noneOf(E::class.java).also { result ->
e.forEach { result.add(it) }
}
fun writeLogger(levels: Array<out LogLevel>, stringConsumer: Consumer<String>) = Logger { msg, severity ->
if (severity in enumSetOf(levels)) {
stringConsumer.accept(msg)
}
}
fun consoleLogger(vararg levels: LogLevel) =
writeLogger(levels) { msg -> System.err.println("Writing to console: $msg") }
fun emailLogger(vararg levels: LogLevel) =
writeLogger(levels) { msg -> System.err.println("Sending via email: $msg") }
fun fileLogger(vararg levels: LogLevel) =
writeLogger(levels) { msg -> System.err.println("Writing to Log File: $msg") }
fun main() {
// Build an immutable chain of responsibility
val logger = consoleLogger(*LogLevel.all())
.appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
.appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR))
// Handled by consoleLogger since the console has a LogLevel of all
logger.message("Entering function ProcessOrder().", LogLevel.DEBUG)
logger.message("Order record retrieved.", LogLevel.INFO)
// Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error & Functional_Message
logger.message("Unable to Process Order ORD1 Dated D1 For Customer C1.", LogLevel.FUNCTIONAL_ERROR)
logger.message("Order Dispatched.", LogLevel.FUNCTIONAL_MESSAGE)
// Handled by consoleLogger and fileLogger since fileLogger implements Warning & Error
logger.message("Customer Address details missing in Branch DataBase.", LogLevel.WARNING)
logger.message("Customer Address details missing in Organization DataBase.", LogLevel.ERROR)
}

注意:以上内容已经按照你的要求进行了翻译,只包含了代码部分,没有其他额外的内容。

英文:

The key to the best solution here is the usage of Functional interfaces (introduced in Kotlin 1.4)

Also to make code more concise and idiomatic consider using single-expression fuctions, remove types, which could be inferred, and rewrite EnumSet.copyOf(listOf(*levels)) to auxilary function without extra objects creation:

import java.util.*
import java.util.function.Consumer
fun interface Logger {
fun message(msg: String, severity: LogLevel)
}
fun Logger.appendNext(nextLogger: Logger) = Logger { msg, severity -&gt;
message(msg, severity)
nextLogger.message(msg, severity)
}
inline fun &lt;reified E : Enum&lt;E&gt;&gt; enumSetOf(e: Array&lt;out E&gt;): EnumSet&lt;E&gt; =
EnumSet.noneOf(E::class.java).also { result -&gt; e.forEach { result.add(it) } }
fun writeLogger(levels: Array&lt;out LogLevel&gt;, stringConsumer: Consumer&lt;String&gt;) = Logger { msg, severity -&gt;
if (severity in enumSetOf(levels)) {
stringConsumer.accept(msg)
}
}
fun consoleLogger(vararg levels: LogLevel) =
writeLogger(levels) { msg -&gt; System.err.println(&quot;Writing to console: $msg&quot;) }
fun emailLogger(vararg levels: LogLevel) =
writeLogger(levels) { msg -&gt; System.err.println(&quot;Sending via email: $msg&quot;) }
fun fileLogger(vararg levels: LogLevel) =
writeLogger(levels) { msg -&gt; System.err.println(&quot;Writing to Log File: $msg&quot;) }
fun main() {
// Build an immutable chain of responsibility
val logger = consoleLogger(*LogLevel.all())
.appendNext(emailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR))
.appendNext(fileLogger(LogLevel.WARNING, LogLevel.ERROR))
// Handled by consoleLogger since the console has a LogLevel of all
logger.message(&quot;Entering function ProcessOrder().&quot;, LogLevel.DEBUG)
logger.message(&quot;Order record retrieved.&quot;, LogLevel.INFO)
// Handled by consoleLogger and emailLogger since emailLogger implements Functional_Error &amp; Functional_Message
logger.message(&quot;Unable to Process Order ORD1 Dated D1 For Customer C1.&quot;, LogLevel.FUNCTIONAL_ERROR)
logger.message(&quot;Order Dispatched.&quot;, LogLevel.FUNCTIONAL_MESSAGE)
// Handled by consoleLogger and fileLogger since fileLogger implements Warning &amp; Error
logger.message(&quot;Customer Address details missing in Branch DataBase.&quot;, LogLevel.WARNING)
logger.message(&quot;Customer Address details missing in Organization DataBase.&quot;, LogLevel.ERROR)
}

huangapple
  • 本文由 发表于 2020年10月20日 21:17:40
  • 转载请务必保留本文链接:https://go.coder-hub.com/64446006.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定