Kotlin Encapsulation and Procedural Programming

Software developers use the term Encapsulation to refer to grouping related data and behavior into a single unit, usually called a class. The class can be seen as the polar opposite of procedural based programming where data and behavior are treated as two distinct concerns. It should be noted that OOP and procedural programming have their distinct advantages and one should not be thought of as better as the other. Kotlin supports both styles of programming and it’s not uncommon to see a mix of both procedural and OOP programming.

Procedural Programming Example

Let’s with an example of procedural programming. In this example, we are working with a rectangle object. Its data is stored in a map (a data structure that supports key-value pairs) and then we have functions that consume the data.

val rectangle = mutableMapOf("Width" to 10, "Height" to 10, "Color" to "Red")

fun calcArea(shape : Map<String, Any>) : Int {
    return shape["Height"] as Int * shape["Width"] as Int
}

fun toString(shape : Map<String, Any>) : String {
    return "Width = ${shape["Width"]}, Height = ${shape["Height"]}, Color = ${shape["Color"]}, Area = ${calcArea(shape)}"
}

So we begin with the rectangle object that holds some properties of our rectangle: width, height, and color. Two functions follow the creation of the rectangle. They are calcArea and toString. Notice that these are global functions that accept any Map. This is dangerous because we can’t guarantee that the map will have “Width”, “Height”, or “Color” keys. Another issue is our loss of type safety. Since we need to store both Integers and Strings in the rectangle map, our value has to be of type Any, which is the base type in Kotlin.

OOP

Here is the same problem solved with an OOP approach.

class Rectangle(
    var width : Int,
    var height : Int,
    var color : String){

    fun calcArea() = this.width * this.height

    override fun toString() =
            "Width = ${this.width}, Height = ${this.height}, Color = ${this.color}, Area = ${calcArea()}"
}

The OOP solution demonstrates encapsulation because the data and the behavior associated with the data are grouped into a single entity called a class. The data associated with a class are often referred to as “properties” while the behaviors defined in the class are usually called “methods”. The calcArea() and toString() methods are always guaranteed to work because all objects based on the Rectangle class always have width, height, and color. We also do not lose our type safety because we are free to declare each property as a distinct variable within the class along with it’s type.

When the calcArea() and toString() methods are used, the word ‘this’ refers to the object that is calling these methods. You will notice that unlike the procedural program above, there is no Rectangle parameter supplied to calcArea() or toString(). Instead, the ‘this’ keyword is updated to refer to the object that is currently in use.

Tips on how to choose between Procedural and OOP

It should be noted that many software projects mix procedural and OOP programming. It’s also worth mentioning that almost anything that can be done with OOP can most likely be accomplished with procedural program and vice versa. However, some problems can be more easily solved when using procedural rather than OOP and other problems are better solved with OOP.

Procedural

  • Pure functional program: When we work in terms of pure mathematical functions where a function accepts certain inputs and returns certain outputs without side effects
  • Multi-threading: Procedural programming can help solve many challenges found in multi-threading environments. The integrity of mutable data is always concern in multi-threading, so functional programming works well provided the functions are pure functions that do not change data
  • Input and Output: In many cases, using a class to persist or retrieve an object from a data store is overkill. The same is true for printing to standard IO. Java has been heavily criticized for using the System.out.println() to write text to the console. Kotlin simplified this to println()

OOP and Encapsulation

  • GUI Toolkits: Objects representing buttons, windows, web pages, etc are very well modeled as classes
  • Grouping state or behavior: We often find that entities in software have properties or methods that are commonly held by other similar entities. For example, all road vehicles have wheels and move. Trucks are a specialized vehicle that has a box. Four-wheel drive trucks are specialized trucks that have four-wheel drive. We can use OOP to group all of the items common to all vehicles in a Vehicle class. All items common to Trucks can go in a Truck class, and finally, all items used only in four-wheel drive trucks can go in FourByFourTruck
  • Modularization: OOP allows developers to modularize code into smaller and reusable software components. Since the units of code are small pieces in a system, the code is usually easier to maintain.

Putting it together

Below is a working program that demonstrates both procedural programming and OOP.

package ch1

/**
 * This is a shape object without OOP. Notice how the data is separated from the behavior that works on the
 * data. The data is stored in a Map object, which uses key-value pairs. Then we have separate functions that
 * manipulate the data.
 */
val rectangle = mutableMapOf("Width" to 10, "Height" to 10, "Color" to "Red")

fun calcArea(shape : Map<String, Any>) : Int {
    //How can we guarantee that this map object has "Height" and "Width" property?
    return shape["Height"] as Int * shape["Width"] as Int
}

fun toString(shape : Map<String, Any>) : String {
    return "Width = ${shape["Width"]}, Height = ${shape["Height"]}, Color = ${shape["Color"]}, Area = ${calcArea(shape)}"
}

/**
 * This is a class that represents a Rectangle. You will immediately notice it has less code that the
 * non-OOP implementation. That's because the state (width, height, and color) are grouped together with
 * the behavior. Kotlin takes this a step further by providing us with behavior that lets us change
 * width, height, and color. We only need to add calcArea(), which we can guarantee will always work because
 * we know that there will always be width and height. Likewise, we know our toString() will never fail us
 * for the same reason!
 */
class Rectangle(
    var width : Int,
    var height : Int,
    var color : String){

    fun calcArea() = this.width * this.height

    override fun toString() =
            "Width = ${this.width}, Height = ${this.height}, Color = ${this.color}, Area = ${calcArea()}"
}

fun main(args : Array<String>){
    println("Using procedural programming")
    println(toString(rectangle))

    println("Changing width")
    rectangle["Width"] = 15
    println(toString(rectangle))

    println("Changing height")
    rectangle["Height"] = 80
    println(toString(rectangle))

    println("Changing color")
    rectangle["Color"] = "Blue"
    println(toString(rectangle))

    println("\n*****************************\n")
    println("Now using OOP")

    val square = Rectangle(10, 10, "Red")
    println(square)

    println("Changing height")
    square.height = 90
    println(square)

    println("Changing width")
    square.width = 40
    println(square)

    println("Changing color")
    square.color = "Blue"
    println(square)
}

Here is the output

Using procedural programming
Width = 10, Height = 10, Color = Red, Area = 100
Changing width
Width = 15, Height = 10, Color = Red, Area = 150
Changing height
Width = 15, Height = 80, Color = Red, Area = 1200
Changing color
Width = 15, Height = 80, Color = Blue, Area = 1200

*****************************

Now using OOP
Width = 10, Height = 10, Color = Red, Area = 100
Changing height
Width = 10, Height = 90, Color = Red, Area = 900
Changing width
Width = 40, Height = 90, Color = Red, Area = 3600
Changing color
Width = 40, Height = 90, Color = Blue, Area = 3600

2 thoughts on “Kotlin Encapsulation and Procedural Programming”

Leave a comment