Как идиоматически написать метод функционального интерфейса 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)
}
Другие вопросы по тегам