Skip to content

Interfaces and Abstract Classes

Overview

Interfaces and abstract classes are important concepts in Kotlin object-oriented programming. They provide capabilities for code abstraction, polymorphism, and contract definition. This chapter will detail interface definition, implementation, abstract class usage, and their practical applications.

Interface Basics

Interface Definition and Implementation

kotlin
// Basic interface definition
interface Drawable {
    // Abstract method
    fun draw()
    
    // Method with default implementation
    fun getInfo(): String = "This is a drawable object"
    
    // Abstract property
    val color: String
    
    // Property with default implementation
    val isVisible: Boolean
        get() = true
}

// Implementing interface
class Circle(override val color: String, val radius: Double) : Drawable {
    override fun draw() {
        println("Drawing a $color circle with radius $radius")
    }
    
    override fun getInfo(): String {
        return "Circle: color=$color, radius=$radius"
    }
}

class Rectangle(override val color: String, val width: Double, val height: Double) : Drawable {
    override fun draw() {
        println("Drawing a $color rectangle, ${width}x${height}")
    }
    
    // Override property
    override val isVisible: Boolean = width > 0 && height > 0
}

fun main() {
    val shapes: List<Drawable> = listOf(
        Circle("red", 5.0),
        Rectangle("blue", 10.0, 8.0),
        Rectangle("green", 0.0, 5.0)  // Invisible rectangle
    )
    
    shapes.forEach { shape ->
        println(shape.getInfo())
        println("Visibility: ${shape.isVisible}")
        shape.draw()
        println()
    }
}

Multiple Interface Implementation

kotlin
interface Movable {
    fun move(x: Int, y: Int)
    val speed: Double
}

interface Rotatable {
    fun rotate(angle: Double)
    val rotationSpeed: Double
        get() = 1.0
}

interface Scalable {
    fun scale(factor: Double)
}

// Implementing multiple interfaces
class GameObject(
    override val color: String,
    override val speed: Double
) : Drawable, Movable, Rotatable, Scalable {
    
    private var x = 0
    private var y = 0
    private var rotation = 0.0
    private var scaleX = 1.0
    private var scaleY = 1.0
    
    override fun draw() {
        println("Drawing game object at position ($x, $y), rotation ${rotation} degrees, scale ${scaleX}x${scaleY}")
    }
    
    override fun move(x: Int, y: Int) {
        this.x += x
        this.y += y
        println("Moved to position (${this.x}, ${this.y})")
    }
    
    override fun rotate(angle: Double) {
        rotation += angle
        println("Rotated ${angle} degrees, current angle ${rotation} degrees")
    }
    
    override fun scale(factor: Double) {
        scaleX *= factor
        scaleY *= factor
        println("Scaled ${factor}x, current scale ${scaleX}x${scaleY}")
    }
    
    fun getPosition() = Pair(x, y)
    fun getRotation() = rotation
    fun getScale() = Pair(scaleX, scaleY)
}

fun main() {
    val gameObject = GameObject("purple", 5.0)
    
    // Use features from different interfaces
    gameObject.draw()
    gameObject.move(10, 20)
    gameObject.rotate(45.0)
    gameObject.scale(1.5)
    gameObject.draw()
    
    // Polymorphism
    val drawable: Drawable = gameObject
    val movable: Movable = gameObject
    val rotatable: Rotatable = gameObject
    
    println("As Drawable: ${drawable.getInfo()}")
    println("Movement speed: ${movable.speed}")
    println("Rotation speed: ${rotatable.rotationSpeed}")
}

Advanced Interface Features

Resolving Method Conflicts in Interfaces

kotlin
interface A {
    fun foo() {
        println("A.foo()")
    }
    
    fun bar()
}

interface B {
    fun foo() {
        println("B.foo()")
    }
    
    fun bar() {
        println("B.bar()")
    }
}

// Resolving method conflicts
class C : A, B {
    override fun foo() {
        super<A>.foo()  // Call A's implementation
        super<B>.foo()  // Call B's implementation
        println("C.foo()")  // Own implementation
    }
    
    override fun bar() {
        super<B>.bar()  // Choose B's implementation
    }
}

fun main() {
    val c = C()
    c.foo()
    c.bar()
}

Functional Interfaces (SAM Interfaces)

kotlin
// Functional interface (Single Abstract Method)
fun interface StringProcessor {
    fun process(input: String): String
}

fun interface NumberValidator {
    fun validate(number: Int): Boolean
}

fun interface EventHandler<T> {
    fun handle(event: T)
}

// Using functional interfaces
class TextProcessor {
    fun processText(text: String, processor: StringProcessor): String {
        return processor.process(text)
    }
    
    fun validateNumbers(numbers: List<Int>, validator: NumberValidator): List<Int> {
        return numbers.filter { validator.validate(it) }
    }
}

fun main() {
    val textProcessor = TextProcessor()
    
    // Implementing functional interface with lambda expression
    val upperCaseProcessor = StringProcessor { it.uppercase() }
    val trimProcessor = StringProcessor { it.trim() }
    
    val text = "  hello world  "
    println("Original: '$text'")
    println("Uppercase: '${textProcessor.processText(text, upperCaseProcessor)}'")
    println("Trimmed: '${textProcessor.processText(text, trimProcessor)}'")
    
    // Number validation
    val numbers = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
    val evenValidator = NumberValidator { it % 2 == 0 }
    val positiveValidator = NumberValidator { it > 0 }
    
    println("Even numbers: ${textProcessor.validateNumbers(numbers, evenValidator)}")
    println("Positive numbers: ${textProcessor.validateNumbers(numbers, positiveValidator)}")
    
    // Event handling
    val stringHandler: EventHandler<String> = EventHandler { event ->
        println("Handling string event: $event")
    }
    
    val intHandler: EventHandler<Int> = EventHandler { event ->
        println("Handling integer event: $event")
    }
    
    stringHandler.handle("User login")
    intHandler.handle(404)
}

Abstract Classes

Abstract Class Definition and Usage

kotlin
// Abstract class
abstract class Animal(val name: String, val species: String) {
    // Concrete property
    var age: Int = 0
    
    // Abstract property
    abstract val habitat: String
    
    // Concrete methods
    fun eat(food: String) {
        println("$name is eating $food")
    }
    
    fun sleep() {
        println("$name is sleeping")
    }
    
    // Abstract methods
    abstract fun makeSound()
    abstract fun move()
    
    // Open method with default implementation
    open fun getInfo(): String {
        return "$name is a $species, age $age years old, habitat: $habitat"
    }
}

// Inheriting abstract class
class Dog(name: String) : Animal(name, "canine") {
    override val habitat = "land"
    
    override fun makeSound() {
        println("$name barks")
    }
    
    override fun move() {
        println("$name is running")
    }
    
    // Unique method
    fun wagTail() {
        println("$name wags tail")
    }
}

class Fish(name: String, val waterType: String) : Animal(name, "fish") {
    override val habitat = "$waterType water"
    
    override fun makeSound() {
        println("$name blows bubbles")
    }
    
    override fun move() {
        println("$name is swimming")
    }
    
    override fun getInfo(): String {
        return super.getInfo() + ", water type: $waterType"
    }
    
    fun swim() {
        println("$name is swimming in water")
    }
}

class Bird(name: String, val canFly: Boolean) : Animal(name, "bird") {
    override val habitat = "sky and trees"
    
    override fun makeSound() {
        println("$name chirps")
    }
    
    override fun move() {
        if (canFly) {
            println("$name is flying")
        } else {
            println("$name is walking on ground")
        }
    }
    
    fun fly() {
        if (canFly) {
            println("$name soars high")
        } else {
            println("$name cannot fly")
        }
    }
}

fun main() {
    val animals: List<Animal> = listOf(
        Dog("Buddy").apply { age = 3 },
        Fish("Goldie", "freshwater").apply { age = 1 },
        Bird("Tweety", true).apply { age = 2 },
        Bird("Penguin", false).apply { age = 5 }
    )
    
    println("Zoo information:")
    animals.forEach { animal ->
        println(animal.getInfo())
        animal.makeSound()
        animal.move()
        animal.eat("food")
        
        // Type checking and casting
        when (animal) {
            is Dog -> animal.wagTail()
            is Fish -> animal.swim()
            is Bird -> animal.fly()
        }
        
        println()
    }
}

Combining Abstract Classes and Interfaces

kotlin
// Interface defining behavior contract
interface Flyable {
    val maxAltitude: Double
    fun takeOff()
    fun land()
    fun fly(altitude: Double) {
        if (altitude <= maxAltitude) {
            println("Flying at altitude: ${altitude} meters")
        } else {
            println("Exceeds maximum flight altitude!")
        }
    }
}

interface Swimmable {
    val maxDepth: Double
    fun dive()
    fun surface()
    fun swim(depth: Double) {
        if (depth <= maxDepth) {
            println("Swimming at depth: ${depth} meters")
        } else {
            println("Exceeds maximum dive depth!")
        }
    }
}

// Abstract class providing base implementation
abstract class Vehicle(val name: String, val maxSpeed: Double) {
    var currentSpeed = 0.0
        protected set
    
    abstract val fuelType: String
    abstract fun start()
    abstract fun stop()
    
    open fun accelerate(speed: Double) {
        currentSpeed = minOf(currentSpeed + speed, maxSpeed)
        println("$name accelerated to ${currentSpeed} km/h")
    }
    
    open fun brake(speed: Double) {
        currentSpeed = maxOf(currentSpeed - speed, 0.0)
        println("$name decelerated to ${currentSpeed} km/h")
    }
}

// Airplane: both a vehicle and can fly
class Airplane(name: String, maxSpeed: Double, override val maxAltitude: Double) 
    : Vehicle(name, maxSpeed), Flyable {
    
    override val fuelType = "aviation fuel"
    private var isFlying = false
    
    override fun start() {
        println("$name engine starting")
    }
    
    override fun stop() {
        println("$name engine stopping")
        currentSpeed = 0.0
    }
    
    override fun takeOff() {
        if (!isFlying) {
            isFlying = true
            println("$name taking off")
        }
    }
    
    override fun land() {
        if (isFlying) {
            isFlying = false
            println("$name landing")
        }
    }
}

// Submarine: both a vehicle and can swim
class Submarine(name: String, maxSpeed: Double, override val maxDepth: Double) 
    : Vehicle(name, maxSpeed), Swimmable {
    
    override val fuelType = "nuclear fuel"
    private var isSubmerged = false
    
    override fun start() {
        println("$name nuclear reactor starting")
    }
    
    override fun stop() {
        println("$name nuclear reactor stopping")
        currentSpeed = 0.0
    }
    
    override fun dive() {
        if (!isSubmerged) {
            isSubmerged = true
            println("$name diving")
        }
    }
    
    override fun surface() {
        if (isSubmerged) {
            isSubmerged = false
            println("$name surfacing")
        }
    }
}

// Amphibious vehicle: can drive on land and in water
class AmphibiousVehicle(name: String, maxSpeed: Double, override val maxDepth: Double) 
    : Vehicle(name, maxSpeed), Swimmable {
    
    override val fuelType = "gasoline"
    
    override fun start() {
        println("$name engine starting")
    }
    
    override fun stop() {
        println("$name engine stopping")
        currentSpeed = 0.0
    }
    
    override fun dive() {
        println("$name entering water mode")
    }
    
    override fun surface() {
        println("$name returning to land mode")
    }
}

fun main() {
    val vehicles = listOf(
        Airplane("Boeing 747", 900.0, 12000.0),
        Submarine("Nuclear Sub", 50.0, 500.0),
        AmphibiousVehicle("Amphibious Car", 80.0, 10.0)
    )
    
    vehicles.forEach { vehicle ->
        println("=== ${vehicle.name} ===")
        println("Fuel type: ${vehicle.fuelType}")
        println("Max speed: ${vehicle.maxSpeed} km/h")
        
        vehicle.start()
        vehicle.accelerate(50.0)
        
        // Execute specific operations based on type
        when (vehicle) {
            is Flyable -> {
                vehicle.takeOff()
                vehicle.fly(5000.0)
                vehicle.land()
            }
            is Swimmable -> {
                vehicle.dive()
                vehicle.swim(20.0)
                vehicle.surface()
            }
        }
        
        vehicle.brake(30.0)
        vehicle.stop()
        println()
    }
}

Practical Application Examples

Plugin System Design

kotlin
// Plugin interface
interface Plugin {
    val name: String
    val version: String
    val description: String
    
    fun initialize(): Boolean
    fun execute(context: PluginContext): PluginResult
    fun cleanup()
    
    // Default implementation
    fun isCompatible(systemVersion: String): Boolean = true
}

// Plugin context
data class PluginContext(
    val parameters: Map<String, Any>,
    val workingDirectory: String,
    val logger: Logger
)

// Plugin result
sealed class PluginResult {
    object Success : PluginResult()
    data class Error(val message: String, val exception: Throwable? = null) : PluginResult()
    data class Warning(val message: String) : PluginResult()
}

// Logger interface
interface Logger {
    fun info(message: String)
    fun warn(message: String)
    fun error(message: String, exception: Throwable? = null)
}

// Abstract plugin base class
abstract class BasePlugin(
    override val name: String,
    override val version: String,
    override val description: String
) : Plugin {
    
    protected var isInitialized = false
    
    override fun initialize(): Boolean {
        if (isInitialized) return true
        
        return try {
            onInitialize()
            isInitialized = true
            true
        } catch (e: Exception) {
            false
        }
    }
    
    override fun cleanup() {
        if (isInitialized) {
            onCleanup()
            isInitialized = false
        }
    }
    
    protected abstract fun onInitialize()
    protected abstract fun onCleanup()
    
    protected fun validateContext(context: PluginContext, requiredParams: List<String>): Boolean {
        return requiredParams.all { it in context.parameters }
    }
}

// Concrete plugin implementation
class FileProcessorPlugin : BasePlugin(
    name = "File Processor",
    version = "1.0.0",
    description = "Plugin for processing files"
) {
    
    override fun onInitialize() {
        println("Initializing file processor plugin")
    }
    
    override fun onCleanup() {
        println("Cleaning up file processor plugin")
    }
    
    override fun execute(context: PluginContext): PluginResult {
        if (!validateContext(context, listOf("inputFile", "outputFile"))) {
            return PluginResult.Error("Missing required parameters: inputFile, outputFile")
        }
        
        val inputFile = context.parameters["inputFile"] as String
        val outputFile = context.parameters["outputFile"] as String
        
        context.logger.info("Starting file processing: $inputFile")
        
        return try {
            // Simulate file processing
            Thread.sleep(100)
            context.logger.info("File processing complete: $outputFile")
            PluginResult.Success
        } catch (e: Exception) {
            context.logger.error("File processing failed", e)
            PluginResult.Error("File processing failed: ${e.message}", e)
        }
    }
}

class DataValidatorPlugin : BasePlugin(
    name = "Data Validator",
    version = "2.1.0",
    description = "Plugin for validating data formats"
) {
    
    private val validationRules = mutableMapOf<String, (Any) -> Boolean>()
    
    override fun onInitialize() {
        println("Initializing data validator plugin")
        // Add default validation rules
        validationRules["email"] = { value ->
            value is String && value.contains("@") && value.contains(".")
        }
        validationRules["phone"] = { value ->
            value is String && value.matches(Regex("\\d{3}-\\d{3}-\\d{4}"))
        }
    }
    
    override fun onCleanup() {
        println("Cleaning up data validator plugin")
        validationRules.clear()
    }
    
    override fun execute(context: PluginContext): PluginResult {
        if (!validateContext(context, listOf("data", "rules"))) {
            return PluginResult.Error("Missing required parameters: data, rules")
        }
        
        @Suppress("UNCHECKED_CAST")
        val data = context.parameters["data"] as Map<String, Any>
        @Suppress("UNCHECKED_CAST")
        val rules = context.parameters["rules"] as List<String>
        
        val errors = mutableListOf<String>()
        
        for (rule in rules) {
            val validator = validationRules[rule]
            if (validator == null) {
                errors.add("Unknown validation rule: $rule")
                continue
            }
            
            val value = data[rule]
            if (value == null) {
                errors.add("Missing field: $rule")
            } else if (!validator(value)) {
                errors.add("Field validation failed: $rule")
            }
        }
        
        return if (errors.isEmpty()) {
            context.logger.info("Data validation passed")
            PluginResult.Success
        } else {
            context.logger.warn("Data validation failed: ${errors.joinToString(", ")}")
            PluginResult.Warning("Validation failed: ${errors.joinToString(", ")}")
        }
    }
}

// Plugin manager
class PluginManager {
    private val plugins = mutableMapOf<String, Plugin>()
    private val logger = ConsoleLogger()
    
    fun registerPlugin(plugin: Plugin): Boolean {
        return if (plugin.initialize()) {
            plugins[plugin.name] = plugin
            logger.info("Plugin registered: ${plugin.name} v${plugin.version}")
            true
        } else {
            logger.error("Plugin initialization failed: ${plugin.name}")
            false
        }
    }
    
    fun executePlugin(pluginName: String, context: PluginContext): PluginResult? {
        val plugin = plugins[pluginName]
        return if (plugin != null) {
            logger.info("Executing plugin: $pluginName")
            plugin.execute(context)
        } else {
            logger.error("Plugin not found: $pluginName")
            null
        }
    }
    
    fun unregisterPlugin(pluginName: String) {
        plugins[pluginName]?.let { plugin ->
            plugin.cleanup()
            plugins.remove(pluginName)
            logger.info("Plugin unloaded: $pluginName")
        }
    }
    
    fun listPlugins(): List<Plugin> = plugins.values.toList()
}

// Console logger implementation
class ConsoleLogger : Logger {
    override fun info(message: String) {
        println("[INFO] $message")
    }
    
    override fun warn(message: String) {
        println("[WARN] $message")
    }
    
    override fun error(message: String, exception: Throwable?) {
        println("[ERROR] $message")
        exception?.printStackTrace()
    }
}

fun main() {
    val pluginManager = PluginManager()
    val logger = ConsoleLogger()
    
    // Register plugins
    pluginManager.registerPlugin(FileProcessorPlugin())
    pluginManager.registerPlugin(DataValidatorPlugin())
    
    // List all plugins
    println("Registered plugins:")
    pluginManager.listPlugins().forEach { plugin ->
        println("- ${plugin.name} v${plugin.version}: ${plugin.description}")
    }
    println()
    
    // Execute file processor plugin
    val fileContext = PluginContext(
        parameters = mapOf(
            "inputFile" to "input.txt",
            "outputFile" to "output.txt"
        ),
        workingDirectory = "/tmp",
        logger = logger
    )
    
    val fileResult = pluginManager.executePlugin("File Processor", fileContext)
    println("File processing result: $fileResult")
    println()
    
    // Execute data validator plugin
    val validationContext = PluginContext(
        parameters = mapOf(
            "data" to mapOf(
                "email" to "user@example.com",
                "phone" to "123-456-7890"
            ),
            "rules" to listOf("email", "phone")
        ),
        workingDirectory = "/tmp",
        logger = logger
    )
    
    val validationResult = pluginManager.executePlugin("Data Validator", validationContext)
    println("Data validation result: $validationResult")
    println()
    
    // Unload plugins
    pluginManager.unregisterPlugin("File Processor")
    pluginManager.unregisterPlugin("Data Validator")
}

Interface vs Abstract Class

Selection Guide

kotlin
// Scenarios for using interfaces
interface Serializable {
    fun serialize(): String
    fun deserialize(data: String)
}

interface Comparable<T> {
    fun compareTo(other: T): Int
}

// Scenarios for using abstract classes
abstract class DatabaseConnection(val connectionString: String) {
    protected var isConnected = false
    
    // Common connection logic
    fun connect(): Boolean {
        if (isConnected) return true
        
        return try {
            doConnect()
            isConnected = true
            onConnected()
            true
        } catch (e: Exception) {
            onConnectionError(e)
            false
        }
    }
    
    fun disconnect() {
        if (isConnected) {
            doDisconnect()
            isConnected = false
            onDisconnected()
        }
    }
    
    // Methods subclasses must implement
    protected abstract fun doConnect()
    protected abstract fun doDisconnect()
    
    // Optional hook methods
    protected open fun onConnected() {}
    protected open fun onDisconnected() {}
    protected open fun onConnectionError(exception: Exception) {
        println("Connection error: ${exception.message}")
    }
}

// Concrete implementations
class MySQLConnection(connectionString: String) : DatabaseConnection(connectionString) {
    override fun doConnect() {
        println("Connecting to MySQL database: $connectionString")
    }
    
    override fun doDisconnect() {
        println("Disconnecting from MySQL")
    }
    
    override fun onConnected() {
        println("MySQL connection established")
    }
}

class PostgreSQLConnection(connectionString: String) : DatabaseConnection(connectionString) {
    override fun doConnect() {
        println("Connecting to PostgreSQL database: $connectionString")
    }
    
    override fun doDisconnect() {
        println("Disconnecting from PostgreSQL")
    }
}

Best Practices

1. Interface Design Principles

kotlin
// Good interface design: single responsibility
interface UserRepository {
    fun findById(id: String): User?
    fun save(user: User): User
    fun delete(id: String): Boolean
}

interface UserValidator {
    fun validate(user: User): ValidationResult
}

// Avoid: overly large interfaces
interface BadUserService {
    // Data access
    fun findUser(id: String): User?
    fun saveUser(user: User): User
    
    // Validation
    fun validateUser(user: User): Boolean
    
    // Email sending
    fun sendWelcomeEmail(user: User)
    
    // Logging
    fun logUserAction(action: String)
}

2. Using Default Implementations Wisely

kotlin
interface EventListener<T> {
    fun onEvent(event: T)
    
    // Provide default empty implementations
    fun onError(error: Throwable) {
        // Default: don't handle errors
    }
    
    fun onComplete() {
        // Default: don't handle completion event
    }
}

// Users only need to implement methods they care about
class UserEventListener : EventListener<String> {
    override fun onEvent(event: String) {
        println("User event: $event")
    }
    
    // No need to implement onError and onComplete
}

3. Composition Over Inheritance

kotlin
// Use composition and interfaces instead of deep inheritance
interface Engine {
    fun start()
    fun stop()
    val horsepower: Int
}

interface Transmission {
    fun shiftGear(gear: Int)
    val currentGear: Int
}

class Car(
    private val engine: Engine,
    private val transmission: Transmission
) {
    fun start() {
        engine.start()
    }
    
    fun drive() {
        if (transmission.currentGear == 0) {
            transmission.shiftGear(1)
        }
        println("Car driving with ${engine.horsepower} horsepower")
    }
}

Next Steps

After mastering interfaces and abstract classes, let's learn about exception handling in Kotlin.

Next Chapter: Exception Handling

Exercises

  1. Design a graphics drawing system using interfaces to define behaviors of different shapes
  2. Create a payment system that supports multiple payment methods (credit card, PayPal, etc.)
  3. Implement a message queue system that supports different message processing strategies
  4. Design a game character system using abstract classes and interfaces to implement different classes and skills
  5. Create a data access layer that supports operations on multiple database types

Content is for learning and research only.