kotlindesign-patternsfunctional-programmingchain-of-responsibility

Converting Kotlin functional programming code to object oriented classes


I have the code for the design pattern Chain Of Resposibility in functional programming. I'm trying to convert it to regular OOP classes. Following is the working code for the design pattern:

Functional Programming Code

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

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

/**
 * Handler: Contains a method for building a chain of handlers. 
 * It also declares a method for executing a request.
 */
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)
        }
    }
}

/**
 * Base Handler: Base handler is an optional class where you can put the boilerplate
 * code that’s common to all handler classes.
 *
 * @param writeMessage is the container that acts as the next link in the chain of handlers.
 */
fun writeLogger(vararg levels: LogLevel,
    writeMessage: (String) -> Unit
): Logger {
    val specifiedLevelsSet = EnumSet.copyOf(listOf(*levels))
    return Logger { message, requestedLogLevel ->
        if (specifiedLevelsSet.contains(requestedLogLevel)) {
            writeMessage(message)
        }
    }
}

/**
 * Concrete Handlers: Concrete handlers contain the actual code for processing requests.
 */
fun consoleLogger(vararg levels: LogLevel) =
    writeLogger(*levels) { message -> System.err.println("Writing to console: $message") }

fun emailLogger(vararg levels: LogLevel) =
    writeLogger(*levels) { message -> System.err.println("Sending via email: $message") }

fun fileLogger(vararg levels: LogLevel) =
    writeLogger(*levels) { message -> System.err.println("Writing to Log File: $message") }

/**
 * Client:
 */
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 it has been set to log all levels
    logger.message("Entering function ProcessOrder().", LogLevel.DEBUG)
    logger.message("Order record retrieved.", LogLevel.INFO)

    // Handled by consoleLogger and emailLogger since the 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)
}

I converted the functional programming code to the regular classes for learning purposes. I converted fun interface to regular interface and function writeLogger() to abstract class/regular class BaseLogger, consoleLogger() to the class ConsoleLogger, emailLogger() to EmailLogger and so on.


Problem

Following is the OOP code I managed so far but EmailLogger is not getting called for the same main() function written above:


OOP classes

abstract class Logger(vararg levels: LogLevel) {
    private var nextLogger: Logger? = null
    private val specifiedLevelsSet = EnumSet.copyOf(listOf(*levels))

    fun appendNext(nextLogger: Logger): Logger {
        this.nextLogger = nextLogger
        return this
    }

    fun message(message: String, severity: LogLevel) {
        if (specifiedLevelsSet.contains(severity)) {
            log(message)
        }

        nextLogger?.message(message, severity)
    }

    abstract fun log(message: String)
}

class ConsoleLogger(vararg level: LogLevel) : Logger(*level) {
    override fun log(message: String) {
        System.err.println("Writing to console: $message")
    }
}

class EmailLogger(vararg level: LogLevel) : Logger(*level) {
    override fun log(message: String) {
        System.err.println("Sending via email: $message")
    }
}

class FileLogger(vararg level: LogLevel) : Logger(*level) {
    override fun log(message: String) {
        System.err.println("Writing to Log File: $message")
    }
}

Any input would be much appreciated.


Solution

  • Base Handler

    I replaced the fun interface with an abstract class with the chain making capability:

    abstract class Logger(vararg levels: LogLevel) {
        private var nextLogger: Logger? = null
        private val specifiedLevelsSet = EnumSet.copyOf(listOf(*levels))
    
        fun appendNext(nextLogger: Logger) {
            this.nextLogger = nextLogger
        }
    
        fun message(message: String, severity: LogLevel) {
            if (specifiedLevelsSet.contains(severity)) {
                log(message)
            }
    
            nextLogger?.message(message, severity)
        }
    
        abstract fun log(message: String)
    }
    

    Concrete Handlers

    Converted the functions of implementation to various classes:

    class ConsoleLogger(vararg level: LogLevel) : Logger(*level) {
        override fun log(message: String) {
            System.err.println("Writing to console: $message")
        }
    }
    
    class EmailLogger(vararg level: LogLevel) : Logger(*level) {
        override fun log(message: String) {
            System.err.println("Sending via email: $message")
        }
    }
    
    class FileLogger(vararg level: LogLevel) : Logger(*level) {
        override fun log(message: String) {
            System.err.println("Writing to Log File: $message")
        }
    }
    

    Client

    fun main() {
        // Build an immutable chain of responsibility
        val logger = ConsoleLogger(*LogLevel.all())
        val emailLogger = EmailLogger(LogLevel.FUNCTIONAL_MESSAGE, LogLevel.FUNCTIONAL_ERROR)
        val fileLogger = FileLogger(LogLevel.WARNING, LogLevel.ERROR)
    
        logger.appendNext(emailLogger)
        emailLogger.appendNext(fileLogger)
    
        // Handled by consoleLogger since it has been set to log all levels
        logger.message("Entering function ProcessOrder().", LogLevel.DEBUG)
        logger.message("Order record retrieved.", LogLevel.INFO)
    
        // Handled by consoleLogger and emailLogger since the 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)
    }