#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
// 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
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
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)
// 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
// 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
// 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
// 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
// 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
// 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
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
// 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
- Design a graphics drawing system using interfaces to define behaviors of different shapes
- Create a payment system that supports multiple payment methods (credit card, PayPal, etc.)
- Implement a message queue system that supports different message processing strategies
- Design a game character system using abstract classes and interfaces to implement different classes and skills
- Create a data access layer that supports operations on multiple database types