Interfaces provide an abstract point between two entities. They work by defining a set of behaviors that implementing classes are expected to implement. Rather than using a concrete class, a client of a class can use the interface as an abstraction point providing loose coupling between objects. Loose coupling promotes code reuse because a client is free to use any object that implements the interface.
Kotlin interfaces look similar to classes, but use the interface keyword rather than the class keyword.
/** * This is a basic interface called Server. It defines a single abstract method called serveOrder. * All classes that implement Server have to define serveOrder. */ interface Server { fun serveOrder() }
Our server interface defines a single method, serverOrder(). Note that we cannot create an instance of an interface. So it’s not legal to write code such as this:
val server = Server() //ILLEGAL!
We use Server by writing a class that implements Server.
/** * Tina is a class that implements Server. She can be used anywhere a Server is needed. */ class Tina : Server { /** * Since Server doesn't provide a default implementation of serverOrder, it's up to Tina * to provide a method body for this function. */ override fun serveOrder() { println("Tina is serving an order") } }
Since our Server class only defines abstract methods, the Tina class is expected to override the serverOrder() method. We can then use Tina in any variable that expects a Server.
val server : Server = Tina() //OK because Tina is a Server! fun orderUp(s : Server) = s.serveOrder() orderUp(Tina()) //OK because once again, Tina is a Server
Advance Interfaces
Kotlin allows interfaces to have properties and default methods. Here is a Cook interface that we will use an example.
/** * This is a cook interface. Kotlin allows us to define properties in our interfaces but unlike abstract * class, interfaces may not hold state. */ interface Cook { val name : String /** * Interfaces methods can have default implementations */ fun sayHello() = "$name says Hello" fun cook() /** * Notice how this method takes a server object. That means any class that implements Server * may be used for this method. In other words, Cook is coupled to Servers but not to any * class that implements servers. */ fun orderUp(s : Server) = s.serveOrder() }
The Cook interface has a name property. However, it is not allowed to assign a value to name. Interfaces can never hold state (use abstract classes instead). Since the Cook interface has such a property, all classes that implement Cook must override the name property.
Cook also has two other methods that have default implementations. The sayHello() method and the orderUp(Server) method. Classes that implement Cook do not need to provide an implementation of these methods. Notice that cook() is still abstract and must be overrode. Unlike classes, methods on an interface are open by default and classes are free to override them as needed.
Bob is a Cook and therefore implements the Cook interface.
/** * Bob implements the Cook interface. As such, Bob may be used anywhere we need a cook. */ class Bob : Cook { /** * Cook has a name property. Since interfaces can't hold state, Bob has to override the name property */ override val name: String = "Bob" /** * It's up to Bob to override the cook() method also. */ override fun cook() { println("$name is cooking a burger") } /** * Bob is also free to implement any other method in Cook */ override fun sayHello(): String = "$name speaking here. Hello..." }
Since Bob impelments Cook, he has to override the name property and the cook() method. Bob has chosen to override sayHello(), but he didn’t have to. Has Bob ignored the sayHello() method, he would have simply inherited the behavior defined in Cook.
Using Interfaces
Interfaces are used like any other value in Kotlin. Here is a function that uses the Cook and Server interface.
/** * This demoCook function is loosely coupled to Cook and Server. As such, we are free * to use any implementation of Cook or Server */ fun demoCook(c : Cook, s : Server, name: String){ println("Demonstrating Cook with $name") with (c){ println(sayHello()) cook() orderUp(s) } println() }
The demoCook function is free to use any implementation of Cook and Server. We may only have Bob and Tina right now but should we decide to use a different class implementing Cook and Server, we are still free to use this function with this class. That is the major advantage of using interfaces. Code that is written to use an interface rather than a concrete class remains highly flexible and maintainable.
Putting it Together
The following is an example Kotlin program that uses interfaces followed by its output.
package ch5.interfaces /** * This is a basic interface called Server. It defines a single abstract method called serveOrder. * All classes that implement Server have to define serveOrder. */ interface Server { fun serveOrder() } /** * This is a cook interface. Kotlin allows us to define properties in our interfaces but unlike abstract * class, interfaces may not hold state. */ interface Cook { val name : String /** * Interfaces methods can have default implementations */ fun sayHello() = "$name says Hello" fun cook() /** * Notice how this method takes a server object. That means any class that implements Server * may be used for this method. In other words, Cook is coupled to Servers but not to any * class that implements servers. */ fun orderUp(s : Server) = s.serveOrder() } /** * Tina is a class that implements Server. She can be used anywhere a Server is needed. */ class Tina : Server { /** * Since Server doesn't provide a default implementation of serverOrder, it's up to Tina * to provide a method body for this function. */ override fun serveOrder() { println("Tina is serving an order") } } /** * Bob implements the Cook interface. As such, Bob may be used anywhere we need a cook. */ class Bob : Cook { /** * Cook has a name property. Since interfaces can't hold state, Bob has to override the name property */ override val name: String = "Bob" /** * It's up to Bob to override the cook() method also. */ override fun cook() { println("$name is cooking a burger") } /** * Bob is also free to implement any other method in Cook */ override fun sayHello(): String = "$name speaking here. Hello..." } /** * Jimmy is also a cook. Notice how he is only expected to implement name and cook() */ class Jimmy : Cook { override fun cook() { println("$name is cooking a pizza") } override val name: String = "Jimmy" } /** * This demoCook function is loosely coupled to Cook and Server. As such, we are free * to use any implementation of Cook or Server */ fun demoCook(c : Cook, s : Server, name: String){ println("Demonstrating Cook with $name") with (c){ println(sayHello()) cook() orderUp(s) } println() } fun main(args : Array<String>){ val bob = Bob() val jimmy = Jimmy() val tina = Tina() /** * We can implement interfaces with object expressions also */ val jimmyJr = object: Server{ override fun serveOrder() { println("Jimmy Jr is serving an order") } } demoCook(bob, tina, "Bob") demoCook(jimmy, jimmyJr, "Jimmy") }
Output
Demonstrating Cook with Bob Bob speaking here. Hello... Bob is cooking a burger Tina is serving an order Demonstrating Cook with Jimmy Jimmy says Hello Jimmy is cooking a pizza Jimmy Jr is serving an order
One thought on “Kotlin Interfaces”