Kotlin Abstract Factory Pattern

The Abstract Factory pattern builds on the Factory pattern by abstracting the concrete factory implementation behind an abstract class or an interface. It allows us to loosely couple our factories in situations where we are making a family classes. Rather than having one factory class for each concrete class, we can have specialized factories that build a specific set of classes.

The code remains loosely coupled from both the concrete class itself but also from the factory that creates the class. By using the factory itself as an abstraction point, we can add additional factories later on with their own sets of concrete classes. In the end, not only is client code disconnected from the actual object in use, but it’s also loosely coupled from the creation of the object.

This post shows a demonstration of the Abstract Factory Pattern. We have an interface, CookFactory that abstracts two concrete classes. One of the Factory classes is a BurgerCookFactory that produces Cooks who make burgers. The other Factory is a PizzaCookFactory that produces Cooks who make pizzas. The client code uses the Cook interface.

Let’s begin with the interfaces that serve as abstraction points for both Cooks and Factories.

/**
 * Our factories are going to produce objects of type Cook
 */
interface Cook {
    val name : String
    fun cook()
}

/**
 * BurgerCook is made by the BurgerCookFactory
 */
interface BurgerCook : Cook

/**
 * PizzaCook is made by the PizzaCookFactory
 */
interface PizzaCook : Cook

/**
 * The CookFactory is the base interface for our Factory
 */
interface CookFactory {
    fun getInstance(name : String) : Cook
}

We have four interfaces here. Cook is the base interface of all Cook objects. From Cook, we have two subinterfaces called BurgerCook and PizzaCook. The final interface is the CookFactory, which produces all Cook objects. Of course, we now need some concrete classes to go with these interfaces, so let’s begin with BurgerCookFactory.

/**
 * BurgerCookFactory implements CookFactory and serves as a concrete class to
 * make Cooks that make burgers
 */
class BurgerCookFactory : CookFactory {
    override fun getInstance(name: String) : BurgerCook {
       return when (name){
            "Bob" -> Bob()
            "Tina" -> Tina()
            else -> throw IllegalArgumentException("No class available for $name")
        }
    }

    /**
     * Bob is one concrete class
     */
    private class Bob : BurgerCook {
        override val name: String
            get() = "Bob"

        override fun cook() {
            println("Bob cooked One Fish, Two Fish, Red Fish Hamburger")
        }

    }

    private class Tina : BurgerCook {
        override val name: String
            get() = "Tina"

        override fun cook() {
            println("Tina dropped the burger on the floor while cooking it")
        }

    }
}

The above BurgerCookFactory is responsible for making all BurgerCooks. You will notice that it’s making use of covarient return types by returning BurgerCook rather than Cook. The intent behind BurgerCookFactory is to group all classes that implement BurgerCook into this factory class so that this factory is responsible for this family of classes. Inside of BurgerCookFactory, we have two BurgerCooks, Bob and Tina. We are free to add more BurgerCooks later on and need not worry about breaking existing client code.

After making our BurgerCooks, we decided that we need PizzaCooks also. The existing framework we have makes this fairly straightforward. We only need a PizzaCookFactory.

/**
 * This factory is for PizzaCooks
 */
class PizzaCookFactory : CookFactory {
    override fun getInstance(name: String) : PizzaCook {
        return when (name){
            "Jimmy" -> Jimmy()
            "Jr" -> JimmyJr()
            else -> throw IllegalArgumentException("No class available for $name")
        }
    }

    private class Jimmy : PizzaCook {
        override val name: String
            get() = "Jimmy"

        override fun cook() {
            println("Jimmy is cooking a pizza")
        }

    }

    private class JimmyJr : PizzaCook {

        override val name: String
            get() = "Jimmy Junior"

        override fun cook() {
            println("Jimmy Junior started dancing rather than cooking a pizza")
        }
    }
}

The PizzaCookFactory is another Factory class that implements CookFactory. It makes Cooks that implement the PizzaCook interface, which in turn extends the Cook interface. At this point, PizzaCookFactory is usable anywhere Cooks and CookFactory are needed. As our program grows and changes, we can continue to add more factories and cooks when needed.

The Client code for using the factories is really simple. Here is a simple function that accepts a factory and returns a cook.

fun makeCook(factory: CookFactory, name : String) : Cook {
    return factory.getInstance(name)
}

Of course, this is a gross simplification but demonstrates the point. The makeCook function does not care if the factory is BurgerCookFactory or PizzaCookFactory. It only cares that is has a factory of some sort and returns a cook of some sort. Any CookFactory will do and so will all Cooks.

Putting it Together

Here is a complete working program that demonstrates the Abstract Factory Pattern followed by the output.

package ch4.abstractfactory

/**
 * Our factories are going to produce objects of type Cook
 */
interface Cook {
    val name : String
    fun cook()
}

/**
 * BurgerCook is made by the BurgerCookFactory
 */
interface BurgerCook : Cook

/**
 * PizzaCook is made by the PizzaCookFactory
 */
interface PizzaCook : Cook

/**
 * The CookFactory is the base interface for our Factory
 */
interface CookFactory {
    fun getInstance(name : String) : Cook
}

/**
 * BurgerCookFactory implements CookFactory and serves as a concrete class to
 * make Cooks that make burgers
 */
class BurgerCookFactory : CookFactory {
    override fun getInstance(name: String) : BurgerCook {
       return when (name){
            "Bob" -> Bob()
            "Tina" -> Tina()
            else -> throw IllegalArgumentException("No class available for $name")
        }
    }

    /**
     * Bob is one concrete class
     */
    private class Bob : BurgerCook {
        override val name: String
            get() = "Bob"

        override fun cook() {
            println("Bob cooked One Fish, Two Fish, Red Fish Hamburger")
        }

    }

    private class Tina : BurgerCook {
        override val name: String
            get() = "Tina"

        override fun cook() {
            println("Tina dropped the burger on the floor while cooking it")
        }

    }
}

/**
 * This factory is for PizzaCooks
 */
class PizzaCookFactory : CookFactory {
    override fun getInstance(name: String) : PizzaCook {
        return when (name){
            "Jimmy" -> Jimmy()
            "Jr" -> JimmyJr()
            else -> throw IllegalArgumentException("No class available for $name")
        }
    }

    private class Jimmy : PizzaCook {
        override val name: String
            get() = "Jimmy"

        override fun cook() {
            println("Jimmy is cooking a pizza")
        }

    }

    private class JimmyJr : PizzaCook {

        override val name: String
            get() = "Jimmy Junior"

        override fun cook() {
            println("Jimmy Junior started dancing rather than cooking a pizza")
        }
    }
}

fun makeCook(factory: CookFactory, name : String) : Cook {
    return factory.getInstance(name)
}

fun main(args : Array<String>){
    val burgerFactory = BurgerCookFactory()
    val pizzaFactory = PizzaCookFactory()

    var cook = makeCook(burgerFactory, "Bob")
    cook.cook()

    println()
    cook = makeCook(burgerFactory, "Tina")
    cook.cook()

    println()
    cook = makeCook(pizzaFactory, "Jimmy")
    cook.cook()

    println()
    cook = makeCook(pizzaFactory, "Jr")
    cook.cook()
}

Output

Bob cooked One Fish, Two Fish, Red Fish Hamburger

Tina dropped the burger on the floor while cooking it

Jimmy is cooking a pizza

Jimmy Junior started dancing rather than cooking a pizza
Advertisements

Kotlin Factory Pattern

The Factory Pattern is an OOP design pattern where a client requests one object to create an instance of another object for it. The actual object is normally hidden behind an interface to allow for loose coupling, but the factory knows how to produce the correct object. Let’s consider for example a client that needs a product.

The Product is an interface representing a product. The client doesn’t know what sort of product it is, nor should it care. When a product is needed the client can call a method on a ProductFactory object and tell it what sort of product to product. Internally, the ProductFactory parses the arguments and creates an instance of a usually private class the implements the Product interface. The new object is returned to the caller.

In our example, we will have a Cook that is produced by a CookFactory. The client will specify the kind of cook it wants and the CookFactory will produce the correct instance of Cook. The client will never actually know what sort of Cook it is getting, but that should not be of any concern to the client anyway.

/**
 * A Cook interface that is implemented by private classes in the factory
 */
interface Cook {
    val name : String

    fun cook()
}

We can’t directly create a cook because it’s an interface. So to get an instance of a Cook, we need a class that implements this interface. Our CookFactory singleton provides the necessary classes.

/**
 * A singleton class that produces cooks
 */
object CookFactory {
    enum class CookType {BOB, JIMMY}

    /**
     * Called by clients to get a Cook based on the CookType
     */
    fun getCookInstance(type : CookType) : Cook{
        return when (type) {
            CookType.BOB -> Bob()
            CookType.JIMMY -> Jimmy()
        }
    }

    /**
     * Bob is a Cook that is private to the Factory. Only the Factory can
     * create Bob
     */
    private class Bob : Cook {
        override val name: String
            get() = "Bob"

        override fun cook() {
            println("Bob is cooking the Longest Chard Burger\n")
        }
    }

    /**
     * Jimmy is a Cook that is private to the Factory. Only the Factory can
     * create Jimmy
     */
    private class Jimmy : Cook {
        override val name: String
            get() = "Jimmy"

        override fun cook() {
            println("Jimmy is cooking a pizza\n")
        }

    }
    
    //Note that we can add other cooks here also!
}

The CookFactory only has one method: getCookInstance(). It accepts a value from the CookType enum class and returns an instance of Cook based on the input. Right now the cook is either Bob or Jimmy. It’s important to keep in mind that this is providing an abstraction point.

For example, let’s suppose we want to add more cooks as we maintain the program. We can easily tuck away new cooks inside of the CookFactory object or add additional arguments to the CookType enum or the getCookInstance() method. The clients of CookFactory still work as needed since they are working with the Cook interface rather than concrete Cook classes. Since the Factory Pattern is forcing us to program to an interface rather than a type, we are not only loosely coupled, but our code is highly flexible and maintainable.

Putting it Together

package ch4.factory

/**
 * A Cook interface that is implemented by private classes in the factory
 */
interface Cook {
    val name : String

    fun cook()
}

/**
 * A singleton class that produces cooks
 */
object CookFactory {
    enum class CookType {BOB, JIMMY}

    /**
     * Called by clients to get a Cook based on the CookType
     */
    fun getCookInstance(type : CookType) : Cook{
        return when (type) {
            CookType.BOB -> Bob()
            CookType.JIMMY -> Jimmy()
        }
    }

    /**
     * Bob is a Cook that is private to the Factory. Only the Factory can
     * create Bob
     */
    private class Bob : Cook {
        override val name: String
            get() = "Bob"

        override fun cook() {
            println("Bob is cooking the Longest Chard Burger\n")
        }
    }

    /**
     * Jimmy is a Cook that is private to the Factory. Only the Factory can
     * create Jimmy
     */
    private class Jimmy : Cook {
        override val name: String
            get() = "Jimmy"

        override fun cook() {
            println("Jimmy is cooking a pizza\n")
        }

    }

    //Note that we can add other cooks here also!
}

fun main(args : Array<String>){
    val bob = CookFactory.getCookInstance(CookFactory.CookType.BOB)
    val jimmy = CookFactory.getCookInstance(CookFactory.CookType.JIMMY)

    println("Testing Bob")
    bob.cook()

    println("Testing Jimmy")
    jimmy.cook()
}

Output

Testing Bob
Bob is cooking the Longest Chard Burger

Testing Jimmy
Jimmy is cooking a pizza

Kotlin Constructors

Many OOP languages have special code that is used to initialize a class to a valid state. Such code is referred to as a constructor method. The constructor runs when an instance of a class is created.

Like all methods, constructors can have zero or more parameters. Let’s consider the constructor found in the ArrayList class found in java.util.collections.

ArrayList<?> list = new ArrayList<>(); //Default constructor
ArrayList<?> list2 = new ArrayList<>(list); //Secondary constructor

The above Java code snippet demonstrates multiple constructor. The first constructor is called the default constructor and accepts no arguments. It creates an empty ArrayList object. The other constructor takes an existing collection object and automatically adds all objects contained in the collection passed into the constructor into the new list. Let’s walk through the various forms of constructors we can define in Kotlin.

Default Constructors

When no constructor is specified, Kotlin will supply an empty no argument constructor.

class ChalkBoard {
    val message = "New Baconings"
}

val chalkBoard = ChalkBoard()
println(chalkBoard.message)

The ChalkBoard class has no constructor. When we create a ChalkBoard object, we just use the () for the default constructor.

Constructor with Required Parameters

We can also define constructors that force us to use valid data.

class Bob(val position : String)

val bob = Bob("Cook")
println(bob.position)

Notice how the Bob class has a position parameter of type String. When we create an instance of Bob, we have to supply a String to the constructor. There is no default constructor for Bob. Since we used the val keyword in front of the position argument, Kotlin created a position property on Bob. This lets use print Bob’s position in the println statement.

Constructor with Initialization Block

We aren’t limited to just setting properties in a constructor. Kotlin also lets us create an initialization block that runs immediately after our properties have been set.

class Linda(val position : String){
    //This is the initialization block that runs when an instance
    //of this class is created
    init {
        check(position.isNotBlank(), { "Linda needs a job!" })
        println("Linda was created with $position")
    }
}

val linda = Linda("Cook's wife")
println(linda.position)

The above code still set’s Linda’s position property, but when the constructor runs, it will also print “Lnda was created with [specified position]” because of the init block found immedialy after the constructor as long was position is not an empty String. If position is blank, the init block would throw an exception with the message “Linda needs a job!”. Init blocks can do other things besides validation, but validation is certainly a common use case of the init block.

Property Initializers

Kotlin also provides property initialization blocks. Let’s consider Gene.

class Gene(position : String){
    //This is the property initialization section
    val beefSquash = position.toUpperCase()
}

val gene = Gene("Beefsquash")
println(gene.beefSquash)

Gene has a constructor that takes a position, but notice that there is no val or var keyword prior to position. Since the var/val keyword is missing, Kotlin does not generate a position property for Gene. On the first line of the class body, we have val beefSquash = position.toUpperCase(). The code creates a beefSquash property on Gene and sets it to the upper case value of position. This lets us create properties outside of the constructor and initialize them to the arguments found in the constructor.

Access modifiers on Constructors

A common OOP pattern is to create private constructors and use factory or builder objects to create an instance of a class. Our Tina class shows a stripped down example of the factory pattern.

class Tina private constructor(val position : String){

    //We have to use a companion object to make getInstance() visible
    companion object {
        //We can only use the constructor from within Tina because it's private
        fun getInstance(position : String) = Tina(position)
    }
}

//Can't do this because Tina's constructor is private
//val tina = Tina("Cook");

//We can do this
val tina = Tina.getInstance("Itchy Grill Cook")
println(tina.position)

Whenever we need to add access modifiers or annotations to a constructor, the constructor keyword is required. In this case, we have private constructor in front of the parameters of Tina’s constructor. Only code found within the Tina class may use the constructor because private restricts the visibility of the constructor. Tina objects can only be created by invoking the getInstance() method which creates and returns a Tina object.

Multiple Constructors

Koltin doesn’t limit us to using only constructor. We are actually free to have as many constructors as we need.

class Louise (val position: String){

    //Calling this(...) after the colon but before the { will invoke the
    //first constructor
    constructor(position: String, age : Int): this(position){
        println("Inside secondary constructor with $position and $age")
    }
}
//Call the single argument constructor
val lousieSingle = Louise("Bunny Ears")

//Call the secondary constructor
val louise = Louise("Bunny Ears", 10)
println(louise.position)

Louise has a regular constructor, but inside of the class body, we can define additional constructors by using the constructor keyword and then specifying our arguments. We are free to reuse code between our constructors by using the this keyword followed by the parameters of the constructor we wish to use. Kotlin will use function overloading to resolve to the correct constructor (or issue a compiler error if one can’t be found). In the example, we see two different ways to create a Louise object. We can use her primary constructor, or her secondary constructor, which also print “Inside secondary constructor with $position and $age” to the console.

Optional Arguments

The final constructor technique involves using optional arguments to specify a default value if a parameter is ommitted. Let’s take a look at Teddy.

class Teddy (val position : String = "customer", val favoriteFood : String = "burger")

val teddyDefault = Teddy()
println(teddyDefault.position + ", " + teddyDefault.favoriteFood)

val teddyArguments = Teddy(position = "Best Customer", favoriteFood = "Burger of the Day")
println(teddyArguments.position + ", " + teddyArguments.favoriteFood)

val teddyFood = Teddy(favoriteFood = "Burger of the day")
println(teddyFood.position + ", " + teddyArguments.favoriteFood)

val teddyPosition = Teddy(position = "Best Customer")
println(teddyPosition.position + ", " + teddyArguments.favoriteFood)

We actually have four constructors for Teddy. The first one is a no argument constructor that initializes Teddy’s poistion to customer and his favoriteFood to burger. The other constructor let’s us specify Teddy’s posistion and his favoriteFood. The third constructor let’s use specify Teddy’s favoriteFood and use his default position. Finally we can use the constructor that specifies Teddy’s position, but use the default for favorite foods.

Putting it all Together

package ch1.constructors

class ChalkBoard {
    val message = "New Baconings"
}

/**
 * Kotlin class with an empty constructor
 */
class Bob(val position : String)

/**
 * Kotlin class with initialization block
 */
class Linda(val position : String){
    //This is the initialization block that runs when an instance
    //of this class is created
    init {
        check(position.isNotBlank(), { "Linda needs a job!" })
        println("Linda was created with $position")
    }
}

/**
 * Kotlin class with a property initializer
 */
class Gene(position : String){
    //This is the property initialization section
    val beefSquash = position.toUpperCase()
}

/**
 * Kotlin class with private constructor
 */
class Tina private constructor(val position : String){

    //We have to use a companion object to make getInstance() visible
    companion object {
        //We can only use the constructor from within Tina because it's private
        fun getInstance(position : String) = Tina(position)
    }
}

/**
 * Kotlin class with multiple constructors
 */
class Louise (val position: String){

    //Calling this(...) after the colon but before the { will invoke the
    //first constructor
    constructor(position: String, age : Int): this(position){
        println("Inside secondary constructor with $position and $age")
    }
}

/**
 * Kotlin class that has a constructor with optional arguments. We can use one,
 * both, or none of the arguments when we create an instance of Teddy
 */
class Teddy (val position : String = "customer", val favoriteFood : String = "burger")

fun main(args : Array<String>){
    val chalkBoard = ChalkBoard()
    println(chalkBoard.message)

    val bob = Bob("Cook")
    println(bob.position)

    val linda = Linda("Cook's wife")
    println(linda.position)

    val gene = Gene("Beefsquash")
    println(gene.beefSquash)

    val tina = Tina.getInstance("Itchy Grill Cook")
    println(tina.position)

    val louise = Louise("Bunny Ears", 10)
    println(louise.position)

    val teddyDefault = Teddy()
    println(teddyDefault.position + ", " + teddyDefault.favoriteFood)

    val teddyArguments = Teddy(position = "Best Customer", favoriteFood = "Burger of the Day")
    println(teddyArguments.position + ", " + teddyArguments.favoriteFood)

    val teddyFood = Teddy(favoriteFood = "Burger of the day")
    println(teddyFood.position + ", " + teddyArguments.favoriteFood)

    val teddyPosition = Teddy(position = "Best Customer")
    println(teddyPosition.position + ", " + teddyArguments.favoriteFood)

}

Here’s the output

New Baconings
Cook
Linda was created with Cook's wife
Cook's wife
BEEFSQUASH
Itchy Grill Cook
Inside secondary constructor with Bunny Ears and 10
Bunny Ears
customer, burger
Best Customer, Burger of the Day
customer, Burger of the Day
Best Customer, Burger of the Day

References

https://kotlinlang.org/docs/reference/classes.html

Kotlin Polymorphism

Polymorphism allows computer code to become contextual. In other words, a computer instruction can take on different meanings depending on situation in which the instruction is used. This is no different than how we speak. A person can use the same word, ‘there’, ‘they’re’, or ‘their’ to mean different things even though all three words are said the same way.

Kotlin supports two forms of polymorphism because it is both strongly and statically typed. The first form of polymorphism happens when the code is compiled. The other form happens at runtime. Understanding both forms of polymorphism is critical when writing code in Kotlin.

Compile Time Polymorphism

Let’s consider the following code snippet.

fun printNumber(n : Number){
    println("Using printNumber(n : Number)")
    println(n.toString() + "\n")
}

fun printNumber(n : Int){
    println("Using printNumber(n : Int)")
    println(n.toString() + "\n")
}

fun printNumber(n : Double){
    println("Using printNumber(n : Double)")
    println(n.toString() + "\n")
}

We have three functions all of which have the same name and return type (void). As a matter of fact, the only difference in the signature of these methods is the type of parameter that is used. The first printNumber accepts a Number variable. The second one accepts an Int variable, and the final one accepts a Double variable. This technique is called function overloading.

Since all functions have the same name, how do we know which one will be used when we have code such as the snippet below?

val a : Number = 99
val b = 1
val c = 3.1

printNumber(a) //Which version of printNumber is getting used?
printNumber(b) //Which version of printNumber is getting used?
printNumber(c) //Which version of printNumber is getting used?

The compiler sees three functions with the same name and knows it ultimately has to choose a version of printNumber to use on each statement. Since all three functions have the same name, the compiler turns to context clues to deduce which printNumber function should be used in each case. The variable ‘a’ is explicity declared as type Number. Since there is a version of printNumber that accepts a Number parameter, that version of printNumber is chosen.

The next variable, b, is assigned to one. The number one is an integer, and since we didn’t tell the compiler to make it a Number, it defaults to an Integer type. When printNumber(b) is called, the compiler matches to the version of printNumber that accepts type Integer. The final variable, ‘c’, is initialized to 3.1. The default type for that number is Double so the compiler chooses printNumber(Double) as the correct version to call when ‘c’ is passed as an argument.

It should be noted that when the compiler can’t correctly pick a method, it will issue an error. We are free to overload functions as long as the method signatures are unique enough for the compiler to figure out which function should be used. So for example, functions may have the same return type provided that they take different parameters or a different number of parameters. What isn’t allowed is for two functions to have the same parameters but different return types. In that case, the compiler is unable to figure out which function to use and it will issue an error.

Function overloading is a powerful technique that allows us to write flexible code. The main use case is to write functions that have different behaviors based on the type of object. Imagine writing a function that converts objects to JSON (a form of data exchange). Since every object can be different, we would need different implementations of a functions to correctly output JSON for each type of object. However, it doesn’t really make sense to write things like intToJson, arrayToJson, or carToJson. We can call of these functions by the same name: toJson, and just use polymorphism to call the proper implementation.

Runtime Polymorphism

Runtime polymorphism happens when the program is running. Since Kotlin is an OOP language, we can use classes and interfaces to refer to objects. For example, Number is a class while Integer and Double are both child classes of Number. That it is acceptable to use Integer or Double variables anywhere in our code where Number is expected.

However, things go even futher than using child classes as values for a base class. When a child class overrides a method defined in a base class, the program will use the child class’s method rather than the base class. This is called virtual methods. Let’s consider Number’s toDouble() method.

The toDouble() method is defined in Number, which means all child classes of toDouble() have some sort of an implementation of toDouble(). When we call toDouble() on an Integer, the program knows to use the toDouble() defined in Integer so that the output of toDouble() makes sense for an Integer. Likewise where we to call toDouble() on a BigDecimal object, we would also get the version of that method defined in BigDecimal.

In our code example, let’s consider summing all of our number variables.

fun sum(numbers : List<Number>) : Number {
    return numbers.sumByDouble { it.toDouble() }
}

fun main(args : Array<String>){
    val a : Number = 99
    val b = 1
    val c = 3.1

    //Using runtime polymorphism
    println("Summing all numbers")
    println(sum(listOf(a, b, c)))
}

Since the varaibles ‘a’, ‘b’, and ‘c’ are all either types of Number or child classes of Number, we can use them in the sum function. Inside of the function, we call it.toDouble() to convert the current number into a double. One of those numbers is the variable ‘b’ which is an Integer. So when b.toDouble() gets called, the version associated with Integer is used. However, when c.toDouble() gets called, the version of toDouble() associated with Double gets used.

Putting it together

Both forms of polymorphism allow for highly flexible code. Well designed computer code should be written with generalization in mind. By targeting a base class or an interface, we can reuse the same code with different types of objects by using polymorphism. Likewise, function overloading improves the readability of code and it’s maintainability because we can call a function with the same name as other functions and trust that the proper function is used depending on the context.

Example Program

/**
 * The first 3 functions, all called printNumber, demonstrate function
 * overloading. This is polymorphism that is determined at compile time.
 * Basically, the compiler knows which function to use based on the type of the
 * input parameter n. So if n is an Int, it will use printNumber(n : Int).
 * If n is a double, it will use printNumber(n : Double). For all other numbers,
 * it will use printNumber(n : Number).
 */
fun printNumber(n : Number){
    println("Using printNumber(n : Number)")
    println(n.toString() + "\n")
}

fun printNumber(n : Int){
    println("Using printNumber(n : Int)")
    println(n.toString() + "\n")
}

fun printNumber(n : Double){
    println("Using printNumber(n : Double)")
    println(n.toString() + "\n")
}

/**
 * This function shows runtime polymorphism. In this case, all objects are of type Number.
 * Number has a toDouble() method which is different for each kind of number. However, since
 * all classes that extend Number must implement toDouble(), we can trust that longs, ints, floats,
 * etc can all make the conversion to a double when needed.
 */
fun sum(numbers : List<Number>) : Number {
    return numbers.sumByDouble { it.toDouble() }
}

fun main(args : Array<String>){
    val a : Number = 99
    val b = 1
    val c = 3.1

    //Using compile time polymorphism
    printNumber(a)
    printNumber(b)
    printNumber(c)

    //Using runtime polymorphism
    println("Summing all numbers")
    println(sum(listOf(a, b, c)))
}

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()
}

OOP Abstraction

Abstraction is one of the major components of OOP. When we abstract, we are hiding the internal working details of something from its user. The user only cares about the controls that operate an object, but how the object acts on the controls are of no concern to the user.

A common everyday abstraction that people use daily can be found in a smart phone’s operating system. When a user wishes to make a phone call, they do not worry about how the phone makes a call. All the user cares about is using the keypad to dail a phone number and then pressing the call button. The details of connecting to the cell phone tower and then routing the phone call through the phone network are of no concern to the user. Those details have been abstracted.

Kotlin provides a variety of ways to provide abstraction. In the example below, I used the interface feature to model a Vehicle

interface Vehicle {
    fun park()

    fun drive()

    fun reverse()

    fun start()

    fun shutDown()
}

This code defines an abstraction point for all Vehicles. It guarantees that all classes that implement Vehicle have the following behaviors: park, drive, reverse, start, and shutDown. However, what we do not have is details as to how the Vehicle drives, parks, etc. As a matter of fact, the function bodies of all of the methods inside of vehicle are left empty (they are called abstract methods).

We may wish to take our vehicle for a drive. When we drive our vehicle, we are only really concerned with what the vehicle can do. We don’t care how it parks or goes in reverse. Let’s see this example in terms of code.

fun takeForDrive(v : Vehicle){
    with(v){
        //How we start is abstracted. We only care that the vehicle starts, but
        //we don't care about how it starts.
        start()

        //Likewise, we only care that it goes in reverse(). How it goes in reverse
        //is irrelevant here.
        reverse()

        //And so on...
        drive()
        park()
        shutDown()
    }
}

Notice how the takeForDrive function calls all five of our behaviors on the supplied Vehicle object. It doesn’t even know what kind of a vehicle it is driving. The Vehicle could be a car, Truck, airplane, boat, etc. None of that matters to the takeForDrive function. The details are hidden behind the Vehicle interface (in other words, abstracted).

One of the reasons abstraction is so important is that it promotes code reusability and maintainability. For example, now that we have this takeForDrive function, we can use any object that implements Vehicle. So for example, we can create a Truck class that implements Vehicle.

class Truck : Vehicle {
    override fun park() = println("Truck is parking")

    override fun drive() = println("Truck is driving")

    override fun reverse() = println("Truck is in reverse")

    override fun start() = println("Truck is starting")

    override fun shutDown() = println("Truck is shutting down")
}

and now we can take the Truck for a drive.

val truck = Truck()
takeForDrive(truck)

The price of gas may spike later one and we may choose to drive something that is more efficient. As long as our new mode of transportation implements the Vehicle interface, we can take it for a drive. Here is a car class that impelements Vehicle.

class Car : Vehicle{
    override fun park() = println("Car is parking")

    override fun drive() = println("Car is driving")

    override fun reverse() = println("Car is in reverse")

    override fun start() = println("Car is starting")

    override fun shutDown() = println("Car is shutting down")
}

Just like with truck, we can drive the car.

val car = Car()
takeForDrive(car)

Since Vehicle provides an abstraction point, any code that accepts Vehicle as a parameter can use Truck or Car. The function takeForDrive can be said to be loosely coupled to Truck and Car because it indirectly accepts Trucks or Cars using the Vehicle interface. This makes the takeForDrive function highly reusable to other components that may need to get developed in the future.

Example Program

Here is a fully working Kotlin program that ties everything together.

package ch1

//This defines our public interface for all vehicles
interface Vehicle {
    fun park()

    fun drive()

    fun reverse()

    fun start()

    fun shutDown()
}

//Our Truck class provides an implementation of Vehicle
class Truck : Vehicle {
    override fun park() = println("Truck is parking")

    override fun drive() = println("Truck is driving")

    override fun reverse() = println("Truck is in reverse")

    override fun start() = println("Truck is starting")

    override fun shutDown() = println("Truck is shutting down")
}

//Car provides an alternative implementation of Vehicle
class Car : Vehicle{
    override fun park() = println("Car is parking")

    override fun drive() = println("Car is driving")

    override fun reverse() = println("Car is in reverse")

    override fun start() = println("Car is starting")

    override fun shutDown() = println("Car is shutting down")
}

/**
 * This function demonstrates Abstraction. Notice how it accepts a Vehicle object but
 * makes no distinction if it's a Truck or a Car. The details of how the vehicle parks,
 * drives, reverses, starts, or shuts down are abstracted from this function. In the end, we are
 * only concerned with what the Vehicle object does, not how it does it.
 */
fun takeForDrive(v : Vehicle){
    with(v){
        //How we start is abstracted. We only care that the vehicle starts, but
        //we don't care about how it starts.
        start()

        //Likewise, we only care that it goes in reverse(). How it goes in reverse
        //is irrelevant here.
        reverse()

        //And so on...
        drive()
        park()
        shutDown()
    }
}

fun main(args : Array<String>){
    //Create a new Truck and take it for a drive. It works because Truck
    //implements the Vehicle Interface which abstracts the truck's details from
    //the takeForDrive function
    takeForDrive(Truck())

    //Likewise, we can also take a car for a drive. The car class also implements
    //Vehicle so takeForDrive can also use cars.
    takeForDrive(Car())
}

When run, the program prints

Truck is starting
Truck is in reverse
Truck is driving
Truck is parking
Truck is shutting down
Car is starting
Car is in reverse
Car is driving
Car is parking
Car is shutting down

Kotlin and OOP

Like many JVM languages such as Java, Scala, Groovy, etc, Kotlin supports OOP (Object Orientated Programming). OOP allows developers to create reusable and self-contained software modules known as classes where data and behavior are grouped together and contained within the said class. Such packaging allows developers to think in terms of components when solving a software problem and can improve code reuse and maintainability.

There are often four terminologies that are discussed when explaining OOP. The first term is encapsulation. Encapsulation refers to combining a programs data with the behaviors that operate on the said data. This is different than procedural based programming that treats data and behavior as two seperate concerns. However, encapsulation goes further than just simply grouping behavior and data. It also means that we protect our data inside of the class by only allowing the class itself to use the data. Other users of the class may only work on class data through the class’s public interface.

This takes us into the next concept of OOP, Abstraction. A well designed and encapsulated class functions as a black box. We may use the class, but we may only use it through it’s public interface. The details of how the class works internally are taken away from or Abstracted, from the clients of the class. A car is commonly used as an example of abstraction. We can drive the car using the steering wheel and the foot pedals, but we do not get into the internals of the car and fire the fuel injection at the right time. The car takes care of the details of making it move. We only operate it through its public interface. The details of how a car works are abstracted from us.

OOP promotes code reuse through inheritance. The basic idea is that we can use one class as a template for a more specialized version of a class. For example, we may have a class that represents a Truck. As time went on, we realized that we needed a four wheel drive truck. Rather than writing an entirely new class, we simply create a four wheel drive truck from the truck class. The four wheel drive truck inherits all of the computer code from the truck class, and the developer only needs to focus on code that makes it a four wheel drive truck. Such code reuse not only saves on typing, but it also helps to reduce debugging since developers are free to leverage already tested computer code.

Related to inheritence is polymorphism. Polymorphism is a word that means many-forms. For developers, this means that one object may act as if it were another object. Take the truck example above as an example. Since a four wheel drive truck inherited from truck, the four wheel drive truck may be used whenever the computer code expects a truck. Polymorphism goes a set further in allowing the program to act different depending on the context in which certain portions of computer code are used.

Koltin is a full fleged OOP language (although it does support other programming styles also). The language brings all of the OOP concepts discussed above to the fore-front by allowing us to write classes, abstract their interfaces, extend classes, and even use them in different situations depending on context. Let’s begin by looking at a very basic example of how to write and create a class in Kotlin.

package ch1

class Circle(
        //Define data that gets associated with the class
        private val xPos : Int = 20,
        private val yPos : Int = 20,
        private val radius : Int = 10){

    //Define behavior that uses the data
    override fun toString() : String =
            "center = ($xPos, $yPos) and radius = $radius"
}

fun main(args: Array<String>){
    val c = Circle() //Create a new circle
    val d = Circle(10, 10, 20)
    
    println( c.toString() ) //Call the toString() function on c
    println( d.toString() ) //Call the toString() function on d
}

In the above program, we have a very basic example of a Kotlin class called Circle. The code inside of lines 3-12 tell the Kotlin compiler how to construct objects of Type Circle. The circle has three properties (data): xPos, yPos, and radius. It also has a function that uses the data: toString().

In the bottom half of the program, the main method creates two new circle objects (c and d). The circle c has the default values of 20, 20, and 10 for xPos, yPos, and radius because we used the no parenthesis constructor (). Lines 5-7 in the circle class tell the program to simply use 20, 20, and 10 as default values in this case. Circle d has different valeus for xPos, yPos, and radius because we supplied 10, 10, 20 to the constructor. Thus we have an example of polymorphism in this program because two different constructors were used depending on the program’s context.

When we print on lines 18 and 19, we get two different outputs. When we call c.toString(), we get the String “center = (20, 20) and radius = 10” printed to the console. Calling toString() on d results in “center = (10, 10) and radius = 20”. This works because both c and d are distinct objects in memory and each have there own values for xPos, yPos, and radius. The toString() function acts on each distinct object, and thus, the output of toString() reflects the state of each Circle object.