Published on

OOP Mastery: Encapsulation in Kotlin

Authors

OOP Mastery is a series of self-taught Object-Oriented Programming articles with the Kotlin language, compiled at DanTech0xFF. The purpose of this series is to provide a comprehensive view of Object Orientation in the Kotlin language. This will be a solid foundation for future Programmers if you delve into Android, Mobile Cross Platform, or Backend Java.

  • Part 1: OOP Mastery: Class Types, Interface in Kotlin
  • Part 2: OOP Mastery: Encapsulation in Kotlin
  • Part 3: OOP Mastery: Inheritance in Kotlin
  • Part 4: OOP Mastery: Polymorphism in Kotlin
  • Part 5: OOP Mastery: Abstraction in Kotlin

In this article, we will analyze the Encapsulation of OOP in the Kotlin programming language.

Recalling Encapsulation

Encapsulation is the ability to control the access level of properties within a Class. This is to ensure the integrity of data and logic of a Class to serve the features of a Library or program.

Access Modifiers in Kotlin

Kotlin provides several types of access modifiers.

When declaring without a specific Access modifier, Kotlin will consider it a public access modifier by default.

class DefineKotlinAccessModifier {
  val publicModifier: String = "Public Modifier"
  internal val internalModifier: String = "Internal Modifier"
  protected val protectedModifier: String = "Protected Modifier"
  private val privateModifier: String = "Private Modifier"
}

Kotlin provides programmers with Properties Getter, Setter tools to be more versatile in Encapsulation design.

class EncapsulationKotlin {
    var dynamicString: String = "Initialized Value"
        private set // limits the scope of the set function, dynamicString can be accessed from the outside, but setting must be done inside the Class
    var anotherDynamicString: String = "Another Initialized Value"
        set(value) {
            field = "Another $value"
        } // customize the logic of the set function for anotherDynamicString
}

How to Choose the Appropriate Access Modifier

The general principle in designing a Class is to limit the arbitrary modification of an object's property. This limitation helps to focus the logic of the Class, and only the required behaviors need to be public.

Principle 1: If a variable does not need to be accessed from the outside, use protected or private

class DatabaseService {
  var dbName: String = ""
  // db logics
}
// This is a bad practice when designing the DatabaseService class.
// The dbName variable for storage is marked as public and mutable -> can be changed at runtime.
// The way to improve is

class DatabaseService(private val dbName: String) {
  // Db logics
}
// At this point, the dbName variable stores only one value needed to initialize the database, and cannot be changed over time.
// From the outside, it is not possible to access dbName to control the value of this variable

Principle 2: If a variable needs to be accessed from the outside to read the value, use protected or private for the setter

class DatabaseService(dbName: String) {
    var databaseName: String
        private set

    init {
        databaseName = dbName
    }

    // Db logics
}

Principle 3: If a variable needs to be accessed and set from the outside. Do not set the value directly to the variable, use a setter to control the code better.

class MyWifiController() {
    var currentDeviceCount: Int = 0
        set(value) {
            if (value > maxDeviceCount) {
                println("Device count is too high")
                return
            }
            field = value
        }
    var maxDeviceCount: Int = 10
        set(value) {
            if (currentDeviceCount > value) {
                println("Current device count is greater than max device count")
                return
            }
            field = value
        }
}
// An example in the case where the Programmer needs to force the public setter to be exposed