Kotlin chain of responsibility pattern

How to write a chain of responsibility pattern in Kotlin

An example of a chain of responsibility in Kotlin.

Table of contents for design patterns in Kotlin

In object-oriented design, the chain-of-responsibility pattern is a design pattern consisting of a source of command objects and a series of processing objects.[1] Each processing object contains logic that defines the types of command objects that it can handle; the rest are passed to the next processing object in the chain. A mechanism also exists for adding new processing objects to the end of this chain. Thus, the chain of responsibility is an object oriented version of the if ... else if ... else if ....... else ... endif idiom, with the benefit that the condition–action blocks can be dynamically rearranged and reconfigured at runtime. Wikipedia

Example:

Logger

interface Logger {
    enum class LogLevel {
        INFO, DEBUG, WARNING, ERROR;

        companion object {
            fun all() = values().toSet()
        }

        fun toSet() = setOf(this)
    }

    fun log(level: LogLevel, message: String)
}

AbstractLogger

abstract class AbstractLogger(
    open val levels: Set<Logger.LogLevel>,
    open var next: Logger? = null
) : Logger {

    override fun log(level: Logger.LogLevel, message: String) {
        next?.log(level, message)
    }

    fun appendNext(next: AbstractLogger): AbstractLogger {
        this.next = next
        return next
    }
}

ConsoleLogger

class ConsoleLogger(
    override val levels: Set<Logger.LogLevel>,
    override var next:Logger? = null
): AbstractLogger(levels, next) {
    override fun log(level: Logger.LogLevel, message: String) {
        if (levels.contains(level)) {
            println("Console log: $level $message")
        }
        super.log(level, message)
    }
}

EmailLogger

class EmailLogger(
    override val levels: Set<Logger.LogLevel>,
    override var next:Logger? = null
): AbstractLogger(levels, next) {
    override fun log(level: Logger.LogLevel, message: String) {
        if (levels.contains(level)) {
            println("Email log: $level $message")
        }
        super.log(level, message)
    }
}

FileLogger

class FileLogger(
    override val levels: Set<Logger.LogLevel>,
    override var next:Logger? = null
): AbstractLogger(levels, next) {
    override fun log(level: Logger.LogLevel, message: String) {
        if (levels.contains(level)) {
            println("File log: $level $message")
        }
        super.log(level, message)
    }
}

Usage:

fun main() {
    val logger = ConsoleLogger(all())

    logger.appendNext(EmailLogger(LogLevel.ERROR.toSet()))
        .appendNext(FileLogger(setOf(LogLevel.ERROR, LogLevel.WARNING)))

    logger.log(LogLevel.INFO, "Info log")
    logger.log(LogLevel.DEBUG, "Debug log")
    logger.log(LogLevel.ERROR, "Error log")
    logger.log(LogLevel.WARNING, "Warning log")
}

Result

Console log: INFO Info log
Console log: DEBUG Debug log
Console log: ERROR Error log
Email log: ERROR Error log
File log: ERROR Error log
Console log: WARNING Warning log
File log: WARNING Warning log