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 Singletons

Singleton objects is a design pattern that allows for one and only one instance of a class to exist at a time. Singletons are useful when declaring classes that manage a single shared resource such as a logging file or a network connection. We can use a single object to work with such a resource as opposed to have a bunch of objects in memory that needs synchronized access instead.

Kotlin uses object expressions to create singletons. When we declare an object expression at the top level of a Kotlin module, it becomes a singleton. Here is an example.

object Bob {
    val fName = "Bob"
    val lName = "Belcher"

    fun cook(burgerName : String){
        println("Cooking $burgerName")
    }
}

Many readers will notice that the definition of this Bob object looks almost exactly like a class. That is indeed the case as a matter of fact, only we replace the class keyword with object instead.

We can use a singleton by using the object name followed by a method or property.

fun main(args : Array<String>){
    Bob.cook("Pear Goes the Neighborhood")
}

As a matter of fact, Bob is a global object and is used like any other variable. Developers who are familiar with Java may think of these as static methods, but that is not the case. Static methods are associated with a class. In this case, Bob is actually an instance of a Bob class. His methods belong to an instance, not a class itself.

Since Bob is a singleton, he has some rules. We are free to assign Bob to other variables, but we can’t make more Bob’s (he wouldn’t be a singleton otherwise). Bob also has to be initialized right away with a no-argument constructor (after all, he is created by the runtime). Besides those rules, we use singletons just like any other objects. For example, we could have Bob extend classes or even implement interfaces.

By making Bob a singleton, we are ensuring that only one instance of Bob exists in memory. We are free to use singleton objects just like any other Kotlin object, but we are not free to make new instances of a singleton. Singletons are useful for controlling access to shared resources such as thread pools, logging, caching, or other shared resources. Kotlin makes creating singletons easy by using object expressions.

Putting it Together

Here is a demonstration of singletons followed by program output.

object Bob {
    val fName = "Bob"
    val lName = "Belcher"

    fun cook(burgerName : String){
        println("Cooking $burgerName")
    }
}

fun main(args : Array<String>){
    Bob.cook("Pear Goes the Neighborhood")
}

Output

Cooking Pear Goes the Neighborhood

Observer Pattern in Kotlin

JDK provides java.util.Observable and java.util.Observer as an abstract class / interface combination that allows an easy mechanism with which to implement the observer pattern. The framework allows one class to monitor changes in another class and react as needed to the changes. By using the class and interface provided in JDK, we can loosely couple one class to another while still maintaining the observer pattern.

We are going to use three classes in this example: Burger, Bob, and Tina. Bob is a cook who cooks Burgers. Tina is a server who serves the Burgers, and Burger is the object that is passed between Bob and Tina. Bob will notify Tina when a burger is ready, but he doesn’t actually know anything about Tina. All Bob knows (or cares about) is that Tina is an observer.

Let’s begin with both Burger and Bob (since Burger is one line of code).

class Burger(val name: String)

class Bob : Observable() {

    val name = "Bob"

    fun cookBurger(name : String){
        var burger = Burger(name)

        //Call setChanges() prior to calling notifyObservers()
        setChanged() //Inherited from Observable()
        notifyObservers(burger) //Inherited from Observable()
    }
}

We see here that Bob extends Observable. Observable is an abstract class found in the java.util package. In the cookBurger method, Bob creates an new instance of Burger and then calls setChanged(), a method defined in Observable. After calling setChanged(), Bob calls notifyObservers(), which also comes from Observable. All registered Observer objects are notified and since Bob passes the new Burger object as an argument to notifyObservers, they recieve the Burger Bob created.

The Tina class is the receiving end of this pattern. Tina doesn’t know about Bob or any other Observable. She only acts as a receiver for all Observables.

class Tina : Observer{

    val name = "Tina"

    override fun update(o: Observable?, arg: Any?) {
        when (o){
            is Bob -> {
                if (arg is Burger){
                    println("$name is serving the ${arg.name} cooked by ${o.name}")
                }
            }
            else -> println(o?.javaClass.toString())
        }
    }
}

Since Observer is an interface, Tina is forced to implement the update method. The update method has two parameters. The first one is the Observable that is being updated. The other argument is any additional information the Observable passed when calling notifyObservers().

Kotlin distinguishes between nullable and non-null types but Java does not. Anytime we choose to implement Java interfaces or extend abstract classes found in Java, we have to choose if we want null safety or not. There is no way for the Kotlin compiler to know.

In Tina’s case, we have chosen to declare both o and arg as nullable types by adding the question mark (?) after their types. By doing so, we are choosing to accept the null safety offered by the kotlin compiler. We are free to choose otherwise and disregard the null safety, provided we are reasonably sure the types are not going to be null. For example, when I use the Spring framework in Kotlin, I rarely use the null safety because I am usually correct that I am not going to get null from a Spring method.

In this case, I felt it’s better to use the null safety. Observable and Observer are older classes, and we don’t know for sure what may get passed in as parameters to update. Inside of the implementation of update, we have to cast our parameters to the types we need. We can use is operator combined with the when function (see casting). We check if o is Bob and if arg is Burger. When they are, we print out that a Burger is getting served.

There is one final piece to this example. Although Bob and Tina can now work together thanks to Observable and Observer, they need to know about each other. Since Bob extends Observable, he has an addObserver() method that let’s us pass in an instance of Observer.

fun main(args : Array<String>){
    val bob = Bob()
    //Provide Bob and instance of Tina
    bob.addObserver(Tina())

    bob.cookBurger("It takes Bun to Know Bun Burger")
}

Going forward, whenever Bob calls cookBurger, Tina will get notified and serve it.

Putting it Together

Here is the complete program with its output.

package ch4.observer

import java.util.*

class Burger(val name: String)

class Bob : Observable() {

    val name = "Bob"

    fun cookBurger(name : String){
        var burger = Burger(name)

        //Call setChanges() prior to calling notifyObservers()
        setChanged() //Inherited from Observable()
        notifyObservers(burger) //Inherited from Observable()
    }
}

class Tina : Observer{

    val name = "Tina"

    override fun update(o: Observable?, arg: Any?) {
        when (o){
            is Bob -> {
                if (arg is Burger){
                    println("$name is serving the ${arg.name} cooked by ${o.name}")
                }
            }
            else -> println(o?.javaClass.toString())
        }
    }
}

fun main(args : Array<String>){
    val bob = Bob()
    bob.addObserver(Tina())

    bob.cookBurger("It takes Bun to Know Bun Burger")
}

Output

Tina is serving the It takes Bun to Know Bun Burger burger cooked by Bob

Kotlin Interfaces

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

Kotlin Enum Classes

Enum classes are used to restrict choices to a set of predetermined values. For example, you may wish to define a set of color constants such as Red, Green, and Blue. The color constants may later be used as an argument to a function or used in a when function. The compiler checks to make sure that only the allowed values are used when required. The type safety offered by enum classes can really valuable when reducing bugs and since the constants are named, the readability of the code is improved.

Let’s start with a basic definition of enum classes in Kotlin. We are going to begin with a Toppings enum class that offers Ketchup, Lettuce, Mayonnaise, and Tomatoes.

enum class Toppings {
    KETCHUP,
    MAYONNAISE,
    LETTUCE,
    TOMATO
}

We can reference the values in toppings like so

val ketchup = Toppings.KETCHUP
val mayo = Toppings.MAYONNAISE
val lettuce = Toppings.LETTUCE
val tomato = Toppings.TOMATO

or we can even use it as an argument to a function

fun printToppings(t : Toppings){
    if (t == Toppings.KETCHUP){
        print("Ketchup)
    } else {
        print("Not ketchup")
    }
}

Enum classes also work great with the when function

val t = Toppings.LETTUCE

val topping = 
    when (t) {
       Toppings.KETCHUP -> "Ketchup"
       Toppings.MAYONNAISE -> "Mayonnaise"
       Toppings.LETTUCE -> "Lettuce"
       Toppings.TOMATO -> "Tomato"
    }
print(topping)

Enum classes also have a name and ordinal property

val topping = Toppings.MAYONNAISE
println(topping.name) //Prints MAYONNAISE
println(topping.ordinal) //Prints 1

Advanced Enum Classes

Since enum classes are classes, they are free to have properties and methods like any other classes. Here is an example of an enum class with properties.

enum class Burgers(val burgerName : String){
    CAPTAIN_JACK("Captain Pepper Jack Marrow Burger"),
    MY_FARRO_LADY("My Farro Lady Burger"),
    ENDIVE("Endive Had the Time of My Life Burger")
}

Since each constant in the enum class is an instance of the enum class, we need to initialize all properties like we would any other class. We reference the enum’s property just like any other class property

val b = Burgers.CAPTAIN_JACK
println(b.burgerName) //prints Captain Pepper Jack Marrow Burger

We can also define enum classes with methods, including abstract methods. This example shows an enum class where each constant in the enum class overrides the abstract method found in the enum.

enum class Family{

    LOUISE {
        override fun sayCatchPhrase(): String =
                "I can smell fear on you"
    },

    GENE {
        override fun sayCatchPhrase(): String =
                "I can't tell you my full name! You know Mom won't tell me my middle name!"
    },

    TINA {
        override fun sayCatchPhrase(): String =
                "Here's a bunch of numbers that may look random, but they're my phone number"
    },

    LINDA {
        override fun sayCatchPhrase(): String =
                "Teddy will eat whatever you put in front of him. Remember when he ate that receipt?"
    },

    BOB {
        override fun sayCatchPhrase(): String =
                "So that's what a prostate exam is"
    };

    abstract fun sayCatchPhrase() : String
}

We call the method like we would on any other class.

val f = Family.BOB
print(f.sayCatchPhrase()) //Prints So that's what a prostate exam is

Putting it Together

Here is an example program that shows off all three of the enum examples discussed.

package ch4.enumclasses

enum class Toppings {
    KETCHUP,
    MAYONNAISE,
    LETTUCE,
    TOMATO
}

enum class Burgers(val burgerName : String){
    CAPTAIN_JACK("Captain Pepper Jack Marrow Burger"),
    MY_FARRO_LADY("My Farro Lady Burger"),
    ENDIVE("Endive Had the Time of My Life Burger")
}

enum class Family{

    LOUISE {
        override fun sayCatchPhrase(): String =
                "I can smell fear on you"
    },

    GENE {
        override fun sayCatchPhrase(): String =
                "I can't tell you my full name! You know Mom won't tell me my middle name!"
    },

    TINA {
        override fun sayCatchPhrase(): String =
                "Here's a bunch of numbers that my look random, but they're my phone number"
    },

    LINDA {
        override fun sayCatchPhrase(): String =
                "Teddy will eat whatever you put in front of him. Remember when he ate that receipt?"
    },

    BOB {
        override fun sayCatchPhrase(): String =
                "So that's what a prostate exam is"
    };

    abstract fun sayCatchPhrase() : String
}

fun familyDemo(f : Family){
    println("${f.name} says ${f.sayCatchPhrase()}")
}

fun main(args : Array<String>){
    //Enum classes have a name and ordinal property
    println("Printing all values found in Toppings")
    Toppings.values().forEach { println("${it.name}: ${it.ordinal}") }.also { print("\n") }

    //We can also add our own properties like we did in Burgers
    println("Printing all values found in Burgers with burgerName")
    Burgers.values().forEach { println("${it.name}: ${it.ordinal} => ${it.burgerName}") }.also { print("\n")}

    //And we can even call methods on the enum class
    println("Printing all values found in Family with catchPhrase()")
    Family.values().forEach { println("${it.name}: ${it.ordinal} => ${it.sayCatchPhrase()}") }
    println()

    var burger = "Burger with "
    val topping = Toppings.LETTUCE

    //Enums work great with the when function
    burger += when (topping){
        Toppings.KETCHUP -> "ketchup"
        Toppings.MAYONNAISE -> "mayonnaise"
        Toppings.LETTUCE -> "lettuce"
        Toppings.TOMATO -> "tomato"
    }

    println("Burger is => $burger \n")

    val family = Family.GENE
    familyDemo(family)
}

We get the following output when the program is run.

Printing all values found in Toppings
KETCHUP: 0
MAYONNAISE: 1
LETTUCE: 2
TOMATO: 3

Printing all values found in Burgers with burgerName
CAPTAIN_JACK: 0 => Captain Pepper Jack Marrow Burger
MY_FARRO_LADY: 1 => My Farro Lady Burger
ENDIVE: 2 => Endive Had the Time of My Life Burger

Printing all values found in Family with catchPhrase()
LOUISE: 0 => I can smell fear on you
GENE: 1 => I can't tell you my full name! You know Mom won't tell me my middle name!
TINA: 2 => Here's a bunch of numbers that my look random, but they're my phone number
LINDA: 3 => Teddy will eat whatever you put in front of him. Remember when he ate that receipt?
BOB: 4 => So that's what a prostate exam is

References

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

Kotlin Companion Objects

Kotlin has a companion object feature that lets us associate a single object with a class. Companion objects can serve a number of purposes. For example, we may wish to associate methods such as factory methods with a companion object rather than an instance of a class. Likewise, we can use companion object to store constants or count the number of times an instance of a class was created.

Here is an example Burger class that uses a companion object.

class Burger(val name : String) {
    
    //Create and define a companion object.
    companion object SpecialBurgers {
        //Here are some constants
        const val TOP_BUN_BURGER = "Top Bun Burger"
        const val NATIONAL_PASS_THYME_BURGER = "National Pass Thyme Burger"
        const val KALE_MARY_BURGER = "Kale Mary Burger"
        const val SNIPWRECKED_BURGER = "Snipwrecked Burger"

        //Count the number of burgers that were created
        private var _instances = 0
        
        val burgers : Int
            get() = _instances

        fun burgerList() =
                listOf(TOP_BUN_BURGER,
                        NATIONAL_PASS_THYME_BURGER,
                        KALE_MARY_BURGER,
                        SNIPWRECKED_BURGER)
    }

    init {
        check(name in burgerList(), { "$name not in Burger List"})
        
        //Increment the number of burgers created
        _instances++
    }
}

The Burger class has a companion object called SpecialBurgers. Readers will notice that creating a companion object is almost the same as writing a class. The difference is that the companion object is instantiated with the JVM loads the Burger class into memory. Companion objects can have init blocks, but they are not allowed to have constructors. Finally, we are not allowed to create instances of companion objects.

The SpecialBurgers object has four constants, a variable called _instances, and some methods. The init block in Burger increments _instances. It also calls burgerList() found in the companion object to check if the name variable is found in burgerList(). If the name isn’t in burgerList(), the program will throw an exception.

We can use the companion object later on by using the class name followed by the dot (.) syntax. Here is the later half of the program that uses the companion object.

fun main(args : Array<String>){
    val badBurger = "Barley Davidson Burger"

    try {
        //This will throw an exception because of the
        //check in Burger's init block
        val barleyBurger = Burger(badBurger)
    } catch (e : Exception){
        println("$badBurger is not in ${Burger.burgerList()}")
    }

    //We can reference the KALE_MARY_BURGER constant found in 
    //Burger's companion object
    val firstBurger = Burger.KALE_MARY_BURGER
    val kaleBurger = Burger(firstBurger)
    
    //This next line uses the burgers property found in SpecialBurgers to
    //print how many burgers we have created
    println("Created ${kaleBurger.name} and we have served ${Burger.burgers} burgers")

    //We can also reference our burger constants directly
    val snipsBurger = Burger(Burger.SNIPWRECKED_BURGER)
    println("Created ${snipsBurger.name} and we have served ${Burger.burgers} burgers")

    //Finally, the SpecialBurgers is an actual object, so we
    //can store it in a variable when needed
    val burgerCompanion = Burger.SpecialBurgers
    
    val topBunBurger = Burger(burgerCompanion.TOP_BUN_BURGER)
    println("Created ${topBunBurger.name} and we have served ${Burger.burgers} burgers")
}

We use the SpecialBurgers object in a few different ways in main. First, we try and make a barleyBurger. Creating a barleyBurger throws an exception because it’s burger name, “Barley Davidson Burger” is fails the name check found in Burger’s init block.

The next burger we try making is the kaleBurger. First we show how to store a value found in Burger.KALE_MARY_BURGER in a variable. As you can see, it acts just like any other variable. The next line creates an instance of Burger by passing firstBurger to the constructor of Burger. Creating the kaleBurger succeeds and the init block in Burger increments the _instances variable by one. The next line prints out the name of the burger and how many burgers we have served. Notice that it’s Burger.burgers because we are using the burger property found on SpecialBurgers.

The snipsBurger is created by passing Burger.SNIPWRECKED_BURGER direclty into the Burger’s constructor. Once against _instances is incremented and we now have served two specialty burgers. The final burger is created by first assigning SpecialBurgers to a variable first. Since SpecialBurgers is an object, it can be assigned to other references when needed. We create our topBunBurger by using burgerCompanion.TOP_BUN_BURGER.

Putting it Together

Here is the entire program, followed by the output.

package ch4.companionobjects

class Burger(val name : String) {
    companion object SpecialBurgers {
        //Heere are some constants
        const val TOP_BUN_BURGER = "Top Bun Burger"
        const val NATIONAL_PASS_THYME_BURGER = "National Pass Thyme Burger"
        const val KALE_MARY_BURGER = "Kale Mary Burger"
        const val SNIPWRECKED_BURGER = "Snipwrecked Burger"

        //Count the number of burgers that were created
        private var _instances = 0

        val burgers : Int
            get() = _instances

        fun burgerList() =
                listOf(TOP_BUN_BURGER,
                        NATIONAL_PASS_THYME_BURGER,
                        KALE_MARY_BURGER,
                        SNIPWRECKED_BURGER)
    }

    init {
        check(name in burgerList(), { "$name not in Burger List"})

        //Increment the number of burgers created
        _instances++
    }
}

fun main(args : Array<String>){
    val badBurger = "Barley Davidson Burger"

    try {
        //This will throw an exception because of the
        //check in Burger's init block
        val barleyBurger = Burger(badBurger)
    } catch (e : Exception){
        println("$badBurger is not in ${Burger.burgerList()}")
    }

    //We can reference the KALE_MARY_BURGER constant found in
    //Burger's companion object
    val firstBurger = Burger.KALE_MARY_BURGER
    val kaleBurger = Burger(firstBurger)

    //This next line uses the burgers property found in SpecialBurgers to
    //print how many burgers we have created
    println("Created ${kaleBurger.name} and we have served ${Burger.burgers} burgers")

    //We can also reference our burger constants directly
    val snipsBurger = Burger(Burger.SNIPWRECKED_BURGER)
    println("Created ${snipsBurger.name} and we have served ${Burger.burgers} burgers")

    //Finally, the SpecialBurgers is an actual object, so we
    //can store it in a variable when needed
    val burgerCompanion = Burger.SpecialBurgers

    val topBunBurger = Burger(burgerCompanion.TOP_BUN_BURGER)
    println("Created ${topBunBurger.name} and we have served ${Burger.burgers} burgers")
}

This is the output when run.

Barley Davidson Burger is not in [Top Bun Burger, National Pass Thyme Burger, Kale Mary Burger, Snipwrecked Burger]
Created Kale Mary Burger and we have served 1 burgers
Created Snipwrecked Burger and we have served 2 burgers
Created Top Bun Burger and we have served 3 burgers

References

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