Python Advanced Quit Button

Object orientated programming fits extremely well with GUI programming. Using OOP, we can easily make reusable GUI components. This post shows off a quit button that confirms if the user really wants to exit the application. I got the idea from Programming Python: Powerful Object-Oriented Programming. Here is my implementation of the idea followed by the explanation.

from tkinter import *
from tkinter.messagebox import *


class TkQuitButton(Frame):
    def __init__(self, master=None,
                 auto_pack=True,  # Pack the widget automatically?
                 dialog_title='Confirm',  # Title text for the askyesno dialog
                 dialog_message='Are you sure you want to quit?',  # Message for the askyesno dialog
                 button_text='Quit',  # The quit button's text
                 quit_command=Frame.quit,  # Callback command for when the user wants to quit
                 cnf={}, **kw):

        super().__init__(master, cnf, **kw)
        # Store our fields for later user
        self.quit_command = quit_command
        self.dialog_message = dialog_message
        self.dialog_title = dialog_title
        self.quit_button = Button(self, text=button_text, command=self.quit)

        # Notice that self.quit_button is exposed. This can be useful for when
        # the client code needs to configure this frame on its own
        if auto_pack:
            self.pack_widget()
    
    # This let's us override the packing        
    def pack_widget(self):
        self.pack()
        self.quit_button.pack(side=LEFT, expand=YES, fill=BOTH)

    def quit(self):
        # Call the askyesno dialog
        result = askyesno(self.dialog_title, self.dialog_message)
        if result:
            # if they quit, then execute the stored callback command
            self.quit_command(self)


if __name__ == '__main__':
    TkQuitButton().mainloop()

This class extends the Frame class and packs a button into the frame. There are a few configuration properties that can be passed into the constructor. For example, we can auto_pack the widget so that it uses a default packing scheme. We can specifiy a custom title for the askyesno dialog as well as a custom message. The code even lets use customize the text of the button. We can also use a custom quit handler function should we choose to do so.

We can customize how the widget is packed in two different ways. The first way to access the quit_button property and call pack on it directly. This allows client code to change how this widget is packed into their GUIs. Alternatively, we can subclass this class and just override the pack_widget method.

The default quit implementation uses Tk’s askyesno dialog function to display a confirmation dialog to the user. It’s title and message are set to self.dialog_title and self.dialog_message properties. This allows use to customize what the user sees when the dialog is displayed. If the user presses yes, then we call the self.quit_command function which defaults to Frame.quit. Note that since self.quit is a method, we can customize this behavior by overriding it. Since we use a callback handler to exit the applicaiton, we can also customize how the application exits as well.

Advertisements

Tk – GUI Composition with Classes

Since Python’s Tk widgets are classes, it is really easy to compose GUIs by using Pythons OOP capabilities. OOP works really well because we can break complex GUIs down into smaller components and then compose a larger GUI out of these components. Let’s begin with a text area control.

from tkinter import *


class TextArea(Text):
    def __init__(self, parent=None):
        Text.__init__(self, parent, width=40, height=10, border=2)
        self.pack(expand=YES, fill=BOTH, side=TOP)

if __name__ == '__main__':
    TextArea(Toplevel())
    mainloop()

This code subclasses the Text control and initializes it to what would be a reasonably sized text area control that grows and shrinks with the window. We can verify if our control is working properly by using the self-test code found in the script. Here is a screenshot of what it looks like.

textarea

It’s not much of a window, but we aren’t done composing our GUI yet either. Now let’s make some buttons that will let us load, save, and quit the application. The buttons will be arranged horizontally from left to right. This time, we are going to subclass the Frame class.

class ControlPanel(Frame):
    def __init__(self, parent=None, save=None, load=None, quit_command=exit):
        Frame.__init__(self, parent)
        Button(self, text='Save', command=save).pack(side=LEFT)
        Button(self, text='Load', command=load).pack(side=LEFT)
        Button(self, text='Exit', command=quit_command).pack(side=LEFT)
        self.pack(expand=YES, fill=BOTH, side=TOP)

if __name__ == '__main__':
    ControlPanel(Toplevel())
    mainloop()

In this example, we are using the self variable as a parent object to our 3 objects. The ControlPanel’s constructor accepts three references to functions that act as event handlers for the buttons. Inside of the constructor, we create three buttons and set their text and command attributes. Then we pack them to the left side of the layout. Finally, the frame itself is packed. Running the self-test code gives us the following window.

control_panel

The final task is to combine our controls into a single window. Once again, we are going to subclass Frame.

class TextPanel(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        TextArea(self)
        ControlPanel(self)
        self.pack(expand=YES, fill=BOTH)

if __name__ == '__main__':
    TextPanel(Toplevel())
    mainloop()

Notice how the TextPanel class simply uses TextArea and ControlPanel. Once again, we are using composition to build up a complex GUI. The beauty of this pattern is that we can use both TextArea and ControlPanel in other GUIs. Futhermore, the TextPanel class can also get embedded into other GUIs as well.

Since all three classes have test code, we can easily see how our code is working as we develop. This is part of the reason why it’s so easy to build up GUI applications in Python using Tk or another widget toolkit. We can easily contruct GUIs using OOP and then test then instantly and independently of the application.

Here is the finished GUI followed by a complete script.

complete

from tkinter import *


class TextArea(Text):
    def __init__(self, parent=None):
        Text.__init__(self, parent, width=40, height=10, border=2)
        self.pack(expand=YES, fill=BOTH, side=TOP)


class ControlPanel(Frame):
    def __init__(self, parent=None, save=None, load=None, quit_command=exit):
        Frame.__init__(self, parent)
        Button(self, text='Save', command=save).pack(side=LEFT)
        Button(self, text='Load', command=load).pack(side=LEFT)
        Button(self, text='Exit', command=quit_command).pack(side=LEFT)
        self.pack(expand=YES, fill=BOTH, side=TOP)


class TextPanel(Frame):
    def __init__(self, parent=None):
        Frame.__init__(self, parent)
        TextArea(self)
        ControlPanel(self)
        self.pack(expand=YES, fill=BOTH)


if __name__ == '__main__':
    TextArea(Toplevel())
    ControlPanel(Toplevel())
    TextPanel(Toplevel())
    mainloop()

Tk – Themed Widgets

Since Python Tk widgets are classes, we can use inheritance to specialize widgets for our applications. A common use case is specifying themes for our widgets so that our GUI controls look consistent. In this tutorial, I’ll explain how to make themed Tk widgets.

themed_buttons.py

from tkinter import *


class ThemedFrame(Frame):
    def __init__(self, parent=None, **configs):
        Frame.__init__(self, parent, **configs)
        self.config(bg='Red', borderwidth=10)
        self.pack(expand=YES, fill=BOTH)


class ThemedButton(Button):
    def __init__(self, parent=None, **configs):
        Button.__init__(self, parent, **configs)
        self.config(font=('Arial', 32))
        self.pack()


if __name__ == '__main__':
    frame = ThemedFrame()
    ThemedButton(frame, text='Quit', command=(lambda: sys.exit()))
    frame.mainloop()

The above code makes the following window. The background is red and the button has its font set to Arial 32. All of the ThemedButtons and ThemedFrames in this application will adhere to a consistent styling.

themed_widgets

Making the ThemedFrame and ThemedButton are fairly straightforward. For ThemedFrame, we create a ThemedFrame class and have it extend Frame. Line 6 calls the Frame’s __init__ method and then we start our custom configuration on line 7. In this case, we set the frame’s background to red and give it a border that is 10 pixels thick. Then we pack the frame and set it’s expand and fill options so that the frame always resizes with the window.

ThemedButton follows the same pattern as ThemedFrame. The ThemedButton class extends Button. On line 12, we call Button’s __init__ method followed by configuration options on line 14. In this case, we set the button’s font to Arial 32. Then we call the pack() method.

The demonstration part is found on lines 18-21. We create a ThemedFrame object on line 19. It’s made the same way as a regular Frame. Line 20 makes a ThemedButton. The constructor is consistent with Button’s constructor, so we are free to pass attributes such as the text and callback handlers to the button. Finally, we call mainloop() on ThemedFrame. All of this works because ThemedButton and ThemedFrame are simply specialization of their parent classes.

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

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