Kotlin Interfaces
In object-oriented programming, interfaces are a powerful way to define contracts that classes can implement. Interfaces in Kotlin provide even more capabilities than in some other languages, including default implementations and properties. Let's dive into how interfaces work in Kotlin and why they're an essential part of Kotlin's inheritance system.
What is an Interface?
An interface in Kotlin is a blueprint that defines a set of methods and properties that a class can implement. Unlike abstract classes, interfaces cannot store state (except through properties with backing fields in some cases), but they can define abstract methods, default implementations, and abstract properties.
Think of an interface as a contract: any class that implements the interface promises to provide implementations for everything the interface specifies.
Basic Interface Syntax
Here's how to define a simple interface in Kotlin:
interface Drawable {
fun draw()
fun resize(factor: Float)
}
The interface above declares two abstract methods: draw()
and resize()
. Any class implementing this interface must provide concrete implementations for both methods.
Here's how a class implements this interface:
class Circle(var radius: Double) : Drawable {
override fun draw() {
println("Drawing a circle with radius $radius")
}
override fun resize(factor: Float) {
radius *= factor
println("Circle resized to radius $radius")
}
}
Usage example:
fun main() {
val circle = Circle(5.0)
circle.draw()
circle.resize(2.0f)
circle.draw()
}
Output:
Drawing a circle with radius 5.0
Circle resized to radius 10.0
Drawing a circle with radius 10.0
Properties in Interfaces
Kotlin interfaces can also contain abstract properties:
interface Vehicle {
val maxSpeed: Int
val name: String
get() = "Vehicle" // Property with default getter
}
When implementing this interface, you must provide an implementation for the maxSpeed
property, but you can choose whether to override the name
property:
class Car : Vehicle {
override val maxSpeed: Int = 200
override val name: String = "Sports Car"
}
class Bicycle : Vehicle {
override val maxSpeed: Int = 30
// Using the default implementation for name
}
Usage:
fun main() {
val car = Car()
val bicycle = Bicycle()
println("${car.name} has max speed of ${car.maxSpeed} km/h")
println("${bicycle.name} has max speed of ${bicycle.maxSpeed} km/h")
}
Output:
Sports Car has max speed of 200 km/h
Vehicle has max speed of 30 km/h
Default Method Implementations
One of Kotlin's powerful features is that interfaces can provide default implementations for methods:
interface Logger {
val logLevel: String
fun log(message: String) {
println("[$logLevel] $message")
}
fun debug(message: String) {
if (logLevel == "DEBUG") {
log(message)
}
}
}
Classes implementing this interface can use these default implementations or override them:
class ConsoleLogger(override val logLevel: String) : Logger
class DetailedLogger(override val logLevel: String) : Logger {
override fun log(message: String) {
println("[$logLevel] ${System.currentTimeMillis()}: $message")
}
}
Usage:
fun main() {
val consoleLogger = ConsoleLogger("INFO")
val detailedLogger = DetailedLogger("DEBUG")
consoleLogger.log("This is a regular log message")
detailedLogger.log("This is a detailed log message")
detailedLogger.debug("This is a debug message")
}
Output:
[INFO] This is a regular log message
[DEBUG] 1630428595742: This is a detailed log message
[DEBUG] 1630428595742: This is a debug message
Interface Inheritance
Interfaces can inherit from other interfaces, extending their functionality:
interface Clickable {
fun click()
fun showOff() = println("I'm clickable!")
}
interface Focusable {
fun focus()
fun showOff() = println("I'm focusable!")
}
class Button : Clickable, Focusable {
override fun click() {
println("Button clicked")
}
override fun focus() {
println("Button focused")
}
// Must override showOff because it's inherited from multiple interfaces
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
Usage:
fun main() {
val button = Button()
button.click()
button.focus()
button.showOff()
}
Output:
Button clicked
Button focused
I'm clickable!
I'm focusable!
Notice that when multiple interfaces declare a method with the same signature, the implementing class must override that method. Using the super<Type>
syntax, we can call specific implementations from the parent interfaces.
Interfaces vs Abstract Classes
Understanding when to use interfaces versus abstract classes is important:
Feature | Interface | Abstract Class |
---|---|---|
State | Cannot store state directly | Can have state (properties) |
Constructor | No constructor | Can have constructor |
Implementation | Can have default methods | Can have methods with implementation |
Multiple inheritance | Class can implement multiple interfaces | Class can extend only one abstract class |
Visibility modifiers | Members are public by default | Can use any visibility modifier |
Real-World Example: Payment Systems
Let's see how interfaces can model a payment processing system:
interface PaymentProcessor {
val name: String
val transactionFeePercent: Double
fun processPayment(amount: Double): Boolean
fun calculateFee(amount: Double): Double {
return amount * (transactionFeePercent / 100)
}
}
class CreditCardProcessor : PaymentProcessor {
override val name: String = "Credit Card"
override val transactionFeePercent: Double = 2.5
override fun processPayment(amount: Double): Boolean {
val fee = calculateFee(amount)
println("Processing credit card payment of $${amount} with fee $${fee}")
// Payment processing logic would go here
return true
}
}
class PayPalProcessor : PaymentProcessor {
override val name: String = "PayPal"
override val transactionFeePercent: Double = 3.0
override fun processPayment(amount: Double): Boolean {
val fee = calculateFee(amount)
println("Processing PayPal payment of $${amount} with fee $${fee}")
// Payment processing logic would go here
return true
}
}
class BankTransferProcessor : PaymentProcessor {
override val name: String = "Bank Transfer"
override val transactionFeePercent: Double = 1.0
override fun processPayment(amount: Double): Boolean {
val fee = calculateFee(amount)
println("Processing bank transfer of $${amount} with fee $${fee}")
// Payment processing logic would go here
return true
}
}
A payment service that can use any payment processor:
class PaymentService(private val processor: PaymentProcessor) {
fun makePayment(amount: Double) {
println("Using ${processor.name} processor")
val success = processor.processPayment(amount)
if (success) {
println("Payment successful!")
} else {
println("Payment failed!")
}
}
}
Usage:
fun main() {
val creditCardPayment = PaymentService(CreditCardProcessor())
val paypalPayment = PaymentService(PayPalProcessor())
val bankPayment = PaymentService(BankTransferProcessor())
creditCardPayment.makePayment(100.0)
println("---")
paypalPayment.makePayment(100.0)
println("---")
bankPayment.makePayment(100.0)
}
Output:
Using Credit Card processor
Processing credit card payment of $100.0 with fee $2.5
Payment successful!
---
Using PayPal processor
Processing PayPal payment of $100.0 with fee $3.0
Payment successful!
---
Using Bank Transfer processor
Processing bank transfer of $100.0 with fee $1.0
Payment successful!
This example demonstrates a key benefit of interfaces: they enable polymorphism, where objects of different types can be treated the same way if they implement the same interface. This makes our code more flexible and extensible.
Summary
Kotlin interfaces are a powerful way to define contracts that classes must follow. They offer several benefits:
- They define a clear contract for implementing classes
- They support default implementations for methods
- They allow multiple inheritance
- They enable polymorphic behavior
- They can be used to achieve loose coupling between components
When designing your Kotlin applications, consider using interfaces to define behaviors that might have multiple implementations or to create a clear separation between different parts of your application.
Exercises
-
Create an interface called
Sortable
with a methodsort()
and implement it in classes for different sorting algorithms. -
Define an interface for a data storage system with methods like
save()
,load()
, anddelete()
. Implement this interface for both file-based storage and database storage. -
Create a media player application with an interface for different types of media (audio, video) that all support operations like
play()
,pause()
, andstop()
. -
Implement a notification system using interfaces, supporting email, push notifications, and SMS messages.
Additional Resources
- Kotlin Official Documentation on Interfaces
- Kotlin Interface vs Abstract Class
- Object-Oriented Programming Principles
Happy coding with Kotlin interfaces!
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)