Skip to content
AgileLogics
5 min read

Interfaces

Prerequisites: Inheritance

An interface describes what a class can do — a set of methods and properties — without saying how. Any class can implement as many interfaces as it likes.

Real-world analogy: an interface is a job description ("must be able to drive, must have a license"). Anyone who meets it can do the job, whatever their background.

Example

interface Notifier {
    val channel: String
    fun send(message: String): String

    fun sendAll(messages: List<String>) = messages.map { send(it) }
}

class EmailNotifier(val address: String) : Notifier {
    override val channel = "email"
    override fun send(message: String) = "Email to $address: $message"
}

class SmsNotifier : Notifier {
    override val channel = "sms"
    override fun send(message: String) = "SMS: ${message.take(20)}"
}

fun main() {
    val notifiers: List<Notifier> = listOf(EmailNotifier("ada@example.com"), SmsNotifier())

    for (n in notifiers) {
        println("[${n.channel}] ${n.send("Your order has shipped today")}")
    }

    println(SmsNotifier().sendAll(listOf("Hi", "Bye")))
}

Expected output

[email] Email to ada@example.com: Your order has shipped today
[sms] SMS: Your order has shipp
[SMS: Hi, SMS: Bye]
  • An interface can declare abstract properties (channel) and methods (send) that implementing classes must provide.
  • It can also provide default implementations — sendAll works for every notifier without being written again.
  • Code that uses a Notifier doesn't care which kind it has. Adding a SlackNotifier later needs no change to that code.

Interfaces make code testable

Depending on an interface instead of a concrete class lets tests pass a fake implementation — for example, a FakeNotifier that records messages instead of sending them. You'll use this in the Testing lesson.

Functional interfaces

An interface with a single abstract method, marked fun interface, can be created from a lambda:

fun interface Validator {
    fun isValid(input: String): Boolean
}

fun main() {
    val notBlank = Validator { it.isNotBlank() }
    println(notBlank.isValid("  "))
}
false

Common mistakes

  • Forgetting override on implemented members.
  • Designing one huge interface. Small, focused interfaces are easier to implement and combine.