Welcome to our next lesson! Today, we are focusing on Encapsulation, a crucial pillar of Object-Oriented Programming (OOP). We'll demystify the concept and master its implementation in Kotlin
.
Encapsulation, much like a safe for valuables, ensures that our data in code is accessed and utilized appropriately.
Our exploration itinerary includes an overview of encapsulation, its implementation in Kotlin—specifically using private
properties—and a hands-on tutorial on Accessors (getters and setters). Let's set forth!
Encapsulation wraps data (properties) and the methods manipulating that data into one coherent unit—a class
in Kotlin. Central to Encapsulation is the confinement of data
, which restricts outside access.
Consider your smartphone. Your interaction with it involves buttons; however, the underlying complexities are hidden. That is precisely what encapsulation accomplishes—exposing necessities while concealing complexities.
Encapsulation offers numerous advantages: it safeguards data from unwanted alteration, enhances usability by revealing only pertinent parts, and boosts modularity, thereby making your code more maintainable and adaptable.
Encapsulation in Kotlin is achieved by controlling access to class properties, commonly by marking them as private
. A private
property or method can only be accessed within the class it is declared, safeguarding your class's data from unauthorized access and modification.
Consider the BankAccount
class example to understand encapsulation in action:
Kotlin1class BankAccount { 2 private var balance: Double = 0.0 // balance is private and can't be accessed directly from outside the class 3 4 // Public method to deposit money, ensuring only valid amounts are added 5 fun deposit(amount: Double) { 6 if (amount > 0) { 7 balance += amount 8 printBalance() // Private method can be called within a public method 9 } 10 } 11 12 // Public method to withdraw money, ensuring only valid amounts are subtracted 13 fun withdraw(amount: Double) { 14 if (amount > 0 && balance >= amount) { 15 balance -= amount 16 printBalance() // Private method can be called within a public method 17 } 18 } 19 20 // Private method to print the current balance 21 private fun printBalance() { 22 println("Current balance: $balance") 23 } 24} 25 26fun main() { 27 val account = BankAccount() 28 account.deposit(1000.0) // Prints "Current balance: 1000.0" 29 account.withdraw(500.0) // Prints "Current balance: 500.0" 30 // account.balance // Uncommenting this line will cause a compile error because balance is private 31 // account.printBalance() // This line would also cause a compile error because printBalance is private 32 println("Transaction completed.") 33}
In the BankAccount
class, the balance
property is private, meaning it cannot be directly accessed or modified from outside the class. This ensures that balance
can only be modified through the deposit
and withdraw
methods, which include checks for valid transactions. Additionally, the printBalance
method is private and demonstrates that not only properties but also methods can be encapsulated within a class, further controlling access and preventing unauthorized manipulation. The main
method demonstrates creating an instance of BankAccount
, making deposits and withdrawals, and shows that direct access to both balance
and the printBalance
method is not permitted, reinforcing the concept of encapsulation.
Kotlin automatically provides getters for all properties and setters for mutable properties (declared with var
). Immutable properties (declared with val
) only have a getter, since their values cannot be changed after initialization. To add specific behavior when a property is accessed or modified, such as logging, you can define custom getters and setters. The field
identifier is used within these custom accessors to directly access the property's underlying value.
Consider the User
class that includes a custom getter and setter for the age
property, illustrating the use of field
:
Kotlin1class User(age: Int) { 2 var age = age 3 get(): Int { 4 println("Accessing the age") 5 return field // Uses `field` to access the property's current value 6 } 7 set(value) { 8 println("Setting age to $value") 9 field = value // Updates the property's value 10 } 11 12 var name: String = "Unknown" 13 private set // The setter for name is private and can only be changed within the class 14 15 fun birthday() { 16 age++ // This will trigger the custom setter and print a message 17 println("Happy birthday, you are now $age years old!") 18 } 19} 20 21fun main() { 22 val user = User(30) 23 println("User's current age: ${user.age}") // Prints "Accessing the age" and "User's current age: 30" 24 user.age = 31 // Prints "Setting age to 31" 25 user.birthday() // Increments age and prints a birthday message 26 27 // user.name = "John" // This line will cause a compile error due to the private setter 28 println("User's name: ${user.name}") // Accesses the name using the default getter 29}
In this example, age
is a mutable property with both a custom getter and setter, allowing additional actions (like printing messages) when the property is accessed or changed. The name
property shows how to limit modifications by making the setter private, demonstrating control over property visibility and mutability in Kotlin.
Congratulations! You've successfully navigated through the nuances of encapsulation and understood the role of private properties and accessors in Kotlin.
Draw parallels to the real world—just like how the internal workings of a smartphone are hidden, encapsulation safeguards data within classes.
Next, brace yourself for quick-fire exercises to reinforce your learning. Well done, and continue your coding journey!