Category: Programming

Space to discuss coding in general

  • Encapsulation using primary / secondary constructors

    Encapsulation in Kotlin involves restricting direct access to class variables and exposing them through controlled methods (getters and setters). In Kotlin, you can achieve encapsulation seamlessly using properties with custom accessors or by controlling the visibility of variables.

    Here’s how to add encapsulation to class variables when using primary or secondary constructors:


    1. Encapsulation with Primary Constructor

    The primary constructor can automatically define and initialize class properties with encapsulation using visibility modifiers (private, protected, etc.) and custom getters/setters.

    Example:

    class Person(private var _name: String, private var _age: Int) {
    // Public getter for name
    var name: String
    get() = _name
    set(value) {
    if (value.isNotBlank()) _name = value
    }

    // Public getter and setter for age with validation
    var age: Int
    get() = _age
    set(value) {
    if (value > 0) _age = value
    }

    init {
    println("Person created with Name: $_name, Age: $_age")
    }
    }

    // Usage
    val person = Person("Alice", 25)
    println(person.name) // Accessing name
    person.name = "Bob" // Changing name
    person.age = -5 // Invalid age; won't update
    println("Updated Name: ${person.name}, Age: ${person.age}")

    Explanation:

    • _name and _age are private and cannot be accessed directly outside the class.
    • name and age are public properties with controlled access.
    • Validation logic ensures only valid values are set.

    2. Encapsulation with Secondary Constructor

    For secondary constructors, you can initialize private variables and expose them through public properties.

    Example:

    class Car {
    private var _brand: String = ""
    private var _speed: Int = 0

    // Secondary constructor
    constructor(brand: String, speed: Int) {
    if (brand.isNotBlank()) _brand = brand
    if (speed > 0) _speed = speed
    }

    // Public getter and setter for brand
    var brand: String
    get() = _brand
    set(value) {
    if (value.isNotBlank()) _brand = value
    }

    // Public getter and setter for speed with validation
    var speed: Int
    get() = _speed
    set(value) {
    if (value > 0) _speed = value
    }
    }

    // Usage
    val car = Car("Toyota", 120)
    println("Brand: ${car.brand}, Speed: ${car.speed}")

    car.speed = 150 // Updates speed
    car.brand = "" // Invalid; won't update
    println("Updated Brand: ${car.brand}, Speed: ${car.speed}")

    Explanation:

    • _brand and _speed are private variables initialized in the secondary constructor.
    • brand and speed are public properties with controlled access.

    3. Encapsulation with private set

    Kotlin allows you to make the setter of a property private while keeping the getter public. This ensures that the property can only be modified within the class.

    Example:

    class BankAccount(val accountNumber: String, initialBalance: Double) {
    private var _balance: Double = initialBalance

    // Public read-only property
    val balance: Double
    get() = _balance

    // Public method to update balance
    fun deposit(amount: Double) {
    if (amount > 0) _balance += amount
    }
    }

    // Usage
    val account = BankAccount("12345", 1000.0)
    println("Account Balance: ${account.balance}")

    account.deposit(500.0) // Update balance through deposit method
    println("Updated Balance: ${account.balance}")

    Explanation:

    • balance is read-only outside the class but can be updated internally through the deposit() method.

    4. Encapsulation Using Visibility Modifiers

    Kotlin offers these visibility modifiers:

    • private: Accessible only within the class.
    • protected: Accessible within the class and its subclasses.
    • internal: Accessible within the same module.
    • public: Accessible from anywhere.

    Example:

    class Employee(private val id: Int, private var salary: Double) {
    fun showDetails() {
    println("Employee ID: $id, Salary: $salary")
    }

    fun updateSalary(newSalary: Double) {
    if (newSalary > 0) {
    salary = newSalary
    }
    }
    }

    // Usage
    val emp = Employee(101, 50000.0)
    emp.showDetails()
    // emp.id or emp.salary cannot be accessed directly

    Best Practices

    1. Use private variables to restrict direct access to sensitive data.
    2. Expose data using custom getters and setters to validate or control access.
    3. Use read-only properties (val) for values that shouldn’t change after initialization.
    4. Combine encapsulation with Kotlin’s visibility modifiers for better control.

    😊

  • Primary and Secondary constructors in Kotlin

    In Kotlin, constructors are used to initialize objects when a class is instantiated. Kotlin provides two types of constructors:

    1. Primary Constructor
    2. Secondary Constructor

    1. Primary Constructor

    • The primary constructor is part of the class header and is used to initialize properties of the class.
    • It is concise and typically used when a class has straightforward initialization needs.

    Syntax:

    class ClassName(param1: Type, param2: Type) {
    // Initialization block (if needed)
    init {
    // Code to initialize or process properties
    }
    }

    Example:

    class Person(val name: String, var age: Int) {
    // `init` block to add additional initialization logic
    init {
    println("Name: $name, Age: $age")
    }
    }

    // Create an instance
    val person = Person("Alice", 25)

    Explanation:

    • val name: String and var age: Int in the primary constructor automatically create and initialize properties.
    • The init block executes when the object is created.

    2. Secondary Constructor

    • A secondary constructor is defined inside the class body using the constructor keyword.
    • It provides alternative ways to instantiate the class when additional initialization logic is required or the primary constructor doesn’t fit.

    Syntax:

    class ClassName {
    // Secondary constructor
    constructor(param1: Type, param2: Type) {
    // Code for initialization
    }
    }

    Example:

    class Person {
    var name: String
    var age: Int

    // Secondary constructor
    constructor(name: String, age: Int) {
    this.name = name
    this.age = age
    println("Name: $name, Age: $age")
    }
    }

    // Create an instance
    val person = Person("Bob", 30)

    Explanation:

    • The secondary constructor explicitly initializes the properties name and age.
    • It can contain custom initialization logic specific to the secondary constructor.

    Combining Primary and Secondary Constructors

    When both primary and secondary constructors are present:

    • Secondary constructors must delegate to the primary constructor (directly or indirectly) using the this keyword.

    Example:

    class Person(val name: String, var age: Int) {
    var city: String = "Unknown"

    // Secondary constructor delegating to the primary constructor
    constructor(name: String, age: Int, city: String) : this(name, age) {
    this.city = city
    }

    init {
    println("Primary constructor: Name: $name, Age: $age")
    }
    }

    // Create instances
    val person1 = Person("Charlie", 40) // Uses primary constructor
    val person2 = Person("Dave", 35, "New York") // Uses secondary constructor

    println(person2.city) // Output: New York

    Explanation:

    • The primary constructor initializes name and age.
    • The secondary constructor adds initialization for city and ensures delegation to the primary constructor using : this(name, age).

    Key Differences Between Primary and Secondary Constructors

    FeaturePrimary ConstructorSecondary Constructor
    Definition LocationIn the class header.Inside the class body.
    PurposeSimplifies initialization of properties.Provides alternative ways to create objects.
    DelegationCannot delegate to secondary constructors.Must delegate to the primary constructor.
    Use CasesSimple and common initialization.Complex initialization or alternative setups.

    When to Use Which?

    • Use primary constructors for straightforward property initialization (preferred for most cases).
    • Use secondary constructors when:
      • You need multiple ways to initialize the class.
      • Complex initialization logic is required.

    😊