Как идиоматически написать метод функционального интерфейса Java по умолчанию в Kotlin?
Я пытаюсь идиоматически преобразовать Java-код шаблона проектирования под названием " Цепочка ответственности" в Kotlin. Но я не понимаю, как преобразовать метод по умолчанию
appendNext()
интерфейса Java в Котлине. Я попробовал некоторые уже существующие вопросы, подобные этому и этому, но они, похоже, не работают для моего варианта использования.
Я пробовал преобразовать метод по умолчанию
appendNext()
в функцию расширения в Котлине. Но, видимо, Котлин не находит метода
Logger.message()
и бросает
NoSuchMethodError
.
Я привел исходный код Java и код Kotlin, который я пробовал до сих пор, в следующих фрагментах.
Я бы предпочел идиоматическое решение этого кода на Kotlin без использования
@JvmDefault
аннотация. Код должен быть таким же кратким, как Java, если не больше. Любая помощь приветствуется.
Код Java
Это правильно работающий код Java для шаблона проектирования 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) -> {
message(msg, severity);
nextLogger.message(msg, severity);
};
}
static Logger writeLogger(LogLevel[] levels, Consumer<String> stringConsumer) {
EnumSet<LogLevel> set = EnumSet.copyOf(Arrays.asList(levels));
return (msg, severity) -> {
if (set.contains(severity)) {
stringConsumer.accept(msg);
}
};
}
static Logger consoleLogger(LogLevel... levels) {
return writeLogger(levels, msg -> System.err.println("Writing to console: " + msg));
}
static Logger emailLogger(LogLevel... levels) {
return writeLogger(levels, msg -> System.err.println("Sending via email: " + msg));
}
static Logger fileLogger(LogLevel... levels) {
return writeLogger(levels, msg -> System.err.println("Writing to Log File: " + 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("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);
}
}
Код Котлина
Это то, что я пробовал до сих пор. Я переместил
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 writeLogger(
stringConsumer: Consumer<String>,
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 -> System.err.println("Writing to console: $msg") },
*levels
)
}
fun emailLogger(vararg levels: LogLevel): Logger {
return writeLogger(
Consumer { msg: String -> System.err.println("Sending via email: $msg") },
*levels
)
}
fun fileLogger(vararg levels: LogLevel): Logger {
return writeLogger(
Consumer { msg: String -> System.err.println("Writing to Log File: $msg") },
*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("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)
}
LogLevel.kt
enum class LogLevel {
INFO, DEBUG, WARNING, ERROR, FUNCTIONAL_MESSAGE, FUNCTIONAL_ERROR;
companion object {
public fun all(): Array<LogLevel> {
return values()
}
}
}
2 ответа
Я не понимаю, почему вы добавили
currentLogger
свойство, которого не было в исходном коде Java.
Если вам нужно такое же поведение, как в 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 функции являются типами первого класса. Например, заменить
Consumer<String>
с участием
(String) -> Unit
а затем вызвать его напрямую с помощью
stringConsumer(message)
вместо
stringConsumer.accept(message)
.
Ключом к лучшему решению здесь является использование функциональных интерфейсов (представленных в 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)
}