Kotlin Composition

In the real world, complex systems are built from simple parts. For example, we might decide to make a kitchen. A kitchen would have a refrigerator, a stove, a dishwasher, and countertops. The stove would consist of burgers, switches to turn the burners on and off, and gas values. The gas valve may have a cutoff mechanism and a pressure regulator.

All of these complex objects are created by smaller and simpler components. It is also worth noting that the Kitchen has a stove. The Stove has a burner, and so on. In OOP, the creation of complex objects from simpler components is known as composition. We create classes that are of small concern and scope and then combine them into larger classes.

Kotlin seems to favor composition by the way. You may notice that classes are final by default and that developers need to mark classes as open prior to using inheritance. Likewise, methods need to be marked as open before they can be overridden. Kotlin has a delegation mechanism where the compiler can create delegate methods when using another object. Finally, our interfaces can even have properties and default behaviors. Even if composition wasn’t the intention, all of these mechanisms in the language tend to steer a developer towards composition.

All of which is desirable. We could create classes and add more features to them by using inheritance. Nevertheless, classes in Kotlin are only allowed to have one parent class which means we can’t use two or more parent classes to create a single type. Furthermore, we could end up creating instability in the code as changes in base classes could end up breaking child classes. Finally, unit testing classes become more difficult because do we continue to test superclass behavior in child classes to prevent regressions?

Composition addresses many such issues. Complexe classes use simple objects to break down a problem into manageable scopes. Classes that implement interfaces become loosely coupled and allow for additional maintainability. We can easily unit test small classes and then move onto to unit testing larger classes. Finally, we can swap or add components as needed to keep the code maintainable.

Inheritance describes an is-a relationship. As in, a Truck is-a a vehicle. Composition describes a has-a relationship as in a Cook has-a spatula. We certainly would not want to extend a Cook from a Spatula or a Spatula from a Cook. The relationship would make no sense. Would we really want methods that target Cooks also be able to accept Spatula objects through polymorphism? Should all Cooks get the same properties and behaviors as a Spatula?

We solve such problems by having Cooks own a spatula. Both classes are distinct entities that have different concerns. Our Cooks should not only be able to posses Spatulas but also other cooking utensils as needed. Each utensil may be used for a different purpose. For example, the cook uses the thermometer for checking food temperature and a spatula for flipping a burger patty.

It’s not difficult to model such a relationship in Kotlin. We only need to use classes and interfaces to put such a relationship together. Let’s start with a Utensil interface.

interface Utensil {
    val name : String

    fun interact(f : Food)
}

Now we only need to create two classes that implement the Utensil interface. Here is the thermometer.

class Thermometer : Utensil {
    override val name: String
        get() = "Thermometer"

    override fun interact(f: Food) {
        println("The ${f.name} has a temperature of 160")
    }
}

Followed by the Spatula

class Spatula : Utensil{
    override val name: String
        get() = "Spatula"

    override fun interact(f: Food) {
        println("Flipping the ${f.name}")
    }
}

Our Cook class can use either tool.

class Cook(var utensil: Utensil) {
    val name = "Bob"

    fun cook() : Food {
        val burger = object : Food {
            override val name: String
                get() = "Burger"
        }
        println("Bob is cooking")
        println("Now Bob is using the ${utensil.name}")
        utensil.interact(burger)
        println("Bob is done cooking")
        return burger
    }
}

Since Cook has a Utensil property, he is free to use either the Thermometer or the Spatula. This flexibility would have been almost impossible to model using inheritance and equally difficult to maintain. However, since our Cook possesses Utensil objects, we can freely swap out different utensils as needed.

Putting it Together

Use composition when you need to create complex objects using simple objects. Try and remember that each object should have its own unique concern. Finally, use interfaces when possible to avoid tight coupling between classes. The following program demonstrates a program that uses compositions and interfaces to create a complex object.

package ch5.interfaces.composition

/**
 * We shouldn't tie any object to one specific kind of Food
 */
interface Food {
    val name : String
}

/**
 * Likewise, we shouldn't tie any one specific object to a specific
 * Utensil. We should always try and think generally.
 */
interface Utensil {
    val name : String

    fun interact(f : Food)
}

/**
 * The Spatula is a Utensil
 */
class Spatula : Utensil{
    override val name: String
        get() = "Spatula"

    override fun interact(f: Food) {
        println("Flipping the ${f.name}")
    }
}

/**
 * The Thermmometer is another Utensil
 */
class Thermometer : Utensil {
    override val name: String
        get() = "Thermometer"

    override fun interact(f: Food) {
        println("The ${f.name} has a temperature of 160")
    }
}

/**
 * Cook is a complex object that is made up of Utensils and outputs Food
 */
class Cook(var utensil: Utensil) {
    val name = "Bob"

    fun cook() : Food { //Notice the return type is Food
        val burger = object : Food {
            override val name: String
                get() = "Burger"
        }
        println("Bob is cooking")
        println("Now Bob is using the ${utensil.name}")
        utensil.interact(burger)
        println("Bob is done cooking")
        return burger
    }
}

fun main(args : Array<String>){
    val bob = Cook(Thermometer())
    bob.cook()
    println()

    bob.utensil = Spatula()
    bob.cook()
}

Output

Bob is cooking
Now Bob is using the Thermometer
The Burger has a temperature of 160
Bob is done cooking

Bob is cooking
Now Bob is using the Spatula
Flipping the Burger
Bob is done cooking
Advertisement

Kotlin Inheritance

Inheritance is a core part of Object Orientated programming that provides a powerful code reuse tool by grouping properties and behaviors into common classes (called base classes) and having unique properties and behaviors placed in specific classes that grow out from the base class (called child classes). The child class receives all properties and behavior from its parent class, but it also contains properties and behaviors that are unique to itself. Inheritance allows for specialization of software components when a component has a specific need, but also allows for generalization when using items common to the parent.

Let’s consider an example that is more specific. Suppose we have a Vehicle class that models some sort of vehicle that we can drive. We start by creating a class.

open class Vehicle(
        private val make : String,
        private val model : String,
        private val year : Int) {

    fun start() = println("Starting up the motor")

    fun stop() = println("Turning off the engine")

    fun park() = println("Parking " + toString())

    open fun drive() = println("Driving " + toString())

    open fun reverse() = println("Reversing " + toString())

    override fun toString(): String {
        return "${year} ${make} ${model}"
    }
}

We know that all Vehicles have a make, model, and year. Users of a Vehicle can start and turn off the motor. They can also park, drive, or put the Vehicle in reverse. So far so good. However, later on, we need a vehicle that can tow a camper.

We could add a tow() method to Vehicle, but would that really make sense? What if our Vehicle is a Fiat? Would we really tow a camper with a Fiat? What we need is a Truck. Thanks to Inheritance, we don’t need to write Truck from scratch. We can simply create a specialized version of a Vehicle instead.

open class Truck(make: String,
            model: String,
            year: Int,
            private val towCapacity : Int) : Vehicle(make, model, year) {
    fun tow () = println("${toString()} is towing ${this.towCapacity} lbs")
}

This code creates a Truck class based off of Vehicle. As such, the Truck still has a make, model, and year. It can also start(), stop(), drive(), park(), and reverse() just like any other vehicle. However, it can also tow things and has a towCapacity property. What we have essentially done is reused all of the code from Vehicle and just changed what was needed so that we have a new Vehicle like object that also tows things.

Of course, later on, our needs change again and we decide to go camping in the mountains. It may snow in the mountains, so in addition to being able to tow things, we may want four wheel drive. Once again, not all Trucks have four wheel drive, so we don’t want to add a four wheel drive into Truck. As a matter of fact, four wheel drive isn’t even a specific behavior. What it really does is it enhances the already existing behaviors drive and reverse.

Let’s create another child class, based off of Truck, and specialize the driving behavior.

class FourWheelDrive(make: String, model: String, year: Int, towCapacity: Int) :
        Truck(make, model, year, towCapacity) {

    var fourByFour = false

    override fun drive() {
        if(fourByFour){
            println("Driving ${toString()} in four wheel drive")
        } else {
            super.drive()
        }
    }

    override fun reverse() {
        if(fourByFour){
            println("Reversing ${toString()} in four wheel drive")
        } else {
            super.drive()
        }
    }
}

In this class, we are modifing the already existant behaviors of driving and going in reverse. If the truck has four wheel drived turned on, the output of the program reflects this fact. One the other hand, if four wheel drive is turned off, the methods call super.drive(), which means use the behavior defined in Truck (which bubbles up to the original behavior in Vehicle). Thus, FourWheelDrive has specialized behaviors that were originally found in Vehicle. This is known as overriding behaviors.

Now let’s do a demonstration

fun main(args : Array<String>){
    val car = Vehicle("Fiat", "500", 2012)
    val truck = Truck("Chevy", "Silverado", 2017, 8000)
    val fourWheelDrive = FourWheelDrive("Dodge", "Ram", 2017, 8000)

    //drive() comes from Vehicle
    car.drive()
    println()

    //There is no drive() in Truck, but it 
    //inherited the behavior from Vehicle
    truck.drive()
    println()

    //FourWheelDrive override drive() to customize it
    fourWheelDrive.drive()
    println()

    println("Turn on four wheel drive")
    fourWheelDrive.fourByFour = true
    fourWheelDrive.drive()
}

Output

Driving 2012 Fiat 500

Driving 2017 Chevy Silverado

Driving 2017 Dodge Ram

Turn on four wheel drive
Driving 2017 Dodge Ram in four wheel drive

Three vehicles are made at the beginning of main: car, truck, and fourWheelDrive. They are all of type Vehicle, but Truck is a specialized case of Vehicle and fourWheelDrive is a specialized case of Truck. As such, all three objects have a drive() method which we use in the example program. When fourWheelDrive turns on fourByFour and then invokes drive, the console prints out that it is driving in four wheel drive.

Sealed Class

Although Kotlin supports inheritance, it’s use is discouraged. In order to use inheritance in Kotlin, the ‘open’ keyword needs to be added in front of the ‘class’ keyword first. If the ‘open’ keyword is ommitted, the class is considered to be final and the compiler will not allow the class to be used as a parent class. Likewise, all functions in a class also have to be marked as ‘open’ (see drive and reverse in vehicle), otherwise, overriding behaviors is not permitted.

Why would Kotlin choose to do this while other languages encourage inheritence? After studying issues found with inheritance, it became clear that many developers wrote classes without considering that a class may be extended later on. When changes where made in the parent class, the child classes could potentially break as well. This ended up creating a situation called “fragile base classes”.

Kotlin designers decided that by making developers mark classes as open, it would encourage developers to think about the needs of child classes when working on a base class. Kotlin also has powerful delegation mechanisms that encourage developers to use composition and delegation as code reuse mechanisms rather than inheritance.

Example program

Here is the entire source code used in this post

//Has to be marked as open to allow inheritance
open class Vehicle(
        private val make : String,
        private val model : String,
        private val year : Int) {

    fun start() = println("Starting up the motor")

    fun stop() = println("Turning off the engine")

    fun park() = println("Parking " + toString())

    //Has to be marked as open to allow overriding
    open fun drive() = println("Driving " + toString())

    //Has to be marked as open to allow overriding
    open fun reverse() = println("Reversing " + toString())

    override fun toString(): String {
        return "${year} ${make} ${model}"
    }
}

//Has to be marked as open for inheritance
open class Truck(make: String,
            model: String,
            year: Int,
            private val towCapacity : Int) : Vehicle(make, model, year) {
    fun tow () = println("${toString()} is towing ${this.towCapacity} lbs")
}

//This class is not open and therefore cannot be inherited from
class FourWheelDrive(make: String, model: String, year: Int, towCapacity: Int) :
        Truck(make, model, year, towCapacity) {

    var fourByFour = false

    //The override keyword signals to the compiler that we are overriding
    //the drive() method
    override fun drive() {
        if(fourByFour){
            println("Driving ${toString()} in four wheel drive")
        } else {
            super.drive()
        }
    }

    //The override keyword signals to the compiler that we are overriding
    //the reverse() method
    override fun reverse() {
        if(fourByFour){
            println("Reversing ${toString()} in four wheel drive")
        } else {
            super.drive()
        }
    }
}

fun main(args : Array<String>){
    val car = Vehicle("Fiat", "500", 2012)
    val truck = Truck("Chevy", "Silverado", 2017, 8000)
    val fourWheelDrive = FourWheelDrive("Dodge", "Ram", 2017, 8000)

    //drive() comes from Vehicle
    car.drive()
    println()

    //There is no drive() in Truck, but it
    //inherited the behavior from Vehicle
    truck.drive()
    println()

    //FourWheelDrive override drive() to customize it
    fourWheelDrive.drive()
    println()

    println("Turn on four wheel drive")
    fourWheelDrive.fourByFour = true
    fourWheelDrive.drive()
}
%d bloggers like this: