Program Structure

Overview

This chapter introduces the organizational structure of Kotlin programs, including packages, files, classes, objects, interfaces, and other core concepts. Understanding these structures helps you write well-organized, maintainable Kotlin code.

Package Structure

Package Declaration

// File: src/main/kotlin/com/example/myapp/models/User.kt
package com.example.myapp.models

class User(val name: String, val email: String)

Package Organization Principles

// Organize package structure by functionality
com.example.myapp/
├── models/          // Data models
│   ├── User.kt
│   ├── Product.kt
│   └── Order.kt
├── services/        // Business logic
│   ├── UserService.kt
│   └── OrderService.kt
├── repositories/    // Data access
│   ├── UserRepository.kt
│   └── OrderRepository.kt
├── controllers/     // Controllers (web apps)
│   └── UserController.kt
└── utils/          // Utility classes
    ├── DateUtils.kt
    └── StringUtils.kt

Top-Level Declarations

// File: MathUtils.kt
package com.example.utils

// Top-level function
fun calculateArea(radius: Double): Double = Math.PI * radius * radius

// Top-level property
const val PI_APPROXIMATION = 3.14159

// Top-level class
class Calculator {
    fun add(a: Int, b: Int) = a + b
}

// Top-level object
object MathConstants {
    const val E = 2.71828
    const val GOLDEN_RATIO = 1.61803
}

Class Structure

Basic Class Definition

// Simple class
class Person {
    // Properties
    var name: String = ""
    var age: Int = 0
    
    // Methods
    fun introduce() {
        println("Hi, I'm $name, $age years old")
    }
}

// Class with primary constructor
class Student(val name: String, val studentId: String) {
    // Secondary constructor
    constructor(name: String, studentId: String, grade: Int) : this(name, studentId) {
        this.grade = grade
    }
    
    // Property
    var grade: Int = 1
    
    // Initializer block
    init {
        println("Student $name created with ID $studentId")
    }
    
    // Method
    fun study(subject: String) {
        println("$name is studying $subject")
    }
}

Class Visibility Modifiers

// Default is public
class PublicClass

// Internal visibility (same module)
internal class InternalClass

// Private class (same file)
private class PrivateClass

class Example {
    // Public property
    val publicProperty = "public"
    
    // Internal property
    internal val internalProperty = "internal"
    
    // Protected property (visible to subclasses)
    protected val protectedProperty = "protected"
    
    // Private property
    private val privateProperty = "private"
    
    // Public method
    fun publicMethod() {}
    
    // Private method
    private fun privateMethod() {}
}

Interfaces

Interface Definition and Implementation

// Interface definition
interface Drawable {
    // Abstract property
    val color: String
    
    // Abstract method
    fun draw()
    
    // Method with default implementation
    fun getInfo(): String = "Drawable object with color $color"
    
    // Property with default implementation
    val isVisible: Boolean
        get() = true
}

// Implementing an interface
class Circle(override val color: String, val radius: Double) : Drawable {
    override fun draw() {
        println("Drawing a $color circle with radius $radius")
    }
    
    override fun getInfo(): String {
        return "Circle: ${super.getInfo()}, radius=$radius"
    }
}

// Multiple interface implementation
interface Movable {
    fun move(x: Int, y: Int)
}

class MovableCircle(
    override val color: String,
    val radius: Double
) : Drawable, Movable {
    private var x = 0
    private var y = 0
    
    override fun draw() {
        println("Drawing circle at ($x, $y)")
    }
    
    override fun move(x: Int, y: Int) {
        this.x = x
        this.y = y
    }
}

Functional Interfaces (SAM)

// Functional interface
fun interface StringProcessor {
    fun process(input: String): String
}

// Using lambda to implement
fun main() {
    val upperCaseProcessor = StringProcessor { it.uppercase() }
    val result = upperCaseProcessor.process("hello")
    println(result)  // HELLO
    
    // Pass lambda directly
    processString("world") { it.reversed() }
}

fun processString(input: String, processor: StringProcessor): String {
    return processor.process(input)
}

Object Declarations and Expressions

Object Declaration (Singleton)

// Singleton object
object DatabaseManager {
    private var isConnected = false
    
    fun connect() {
        if (!isConnected) {
            println("Connecting to database...")
            isConnected = true
        }
    }
    
    fun disconnect() {
        if (isConnected) {
            println("Disconnecting from database...")
            isConnected = false
        }
    }
    
    fun isConnected() = isConnected
}

// Using singleton object
fun main() {
    DatabaseManager.connect()
    println("Connected: ${DatabaseManager.isConnected()}")
    DatabaseManager.disconnect()
}

Companion Objects

class User private constructor(val name: String, val email: String) {
    companion object Factory {
        private var userCount = 0
        
        // Factory method
        fun createUser(name: String, email: String): User {
            userCount++
            return User(name, email)
        }
        
        fun createGuestUser(): User {
            return createUser("Guest", "guest@example.com")
        }
        
        // Companion object property
        fun getUserCount() = userCount
    }
    
    override fun toString() = "User(name='$name', email='$email')"
}

fun main() {
    val user1 = User.createUser("Alice", "alice@example.com")
    val user2 = User.createGuestUser()
    
    println("Users created: ${User.getUserCount()}")
    println(user1)
    println(user2)
}

Object Expressions (Anonymous Objects)

interface ClickListener {
    fun onClick()
    fun onLongClick()
}

fun main() {
    // Anonymous object implementing interface
    val button = object : ClickListener {
        override fun onClick() {
            println("Button clicked")
        }
        
        override fun onLongClick() {
            println("Button long clicked")
        }
    }
    
    button.onClick()
    button.onLongClick()
    
    // Anonymous object extending a class
    val customList = object : ArrayList<String>() {
        override fun add(element: String): Boolean {
            println("Adding element: $element")
            return super.add(element)
        }
    }
    
    customList.add("Hello")
    customList.add("World")
}

Enum Classes

Basic Enum

enum class Direction {
    NORTH, SOUTH, EAST, WEST
}

enum class Color(val rgb: Int) {
    RED(0xFF0000),
    GREEN(0x00FF00),
    BLUE(0x0000FF),
    YELLOW(0xFFFF00);
    
    fun getHexString(): String = "#${rgb.toString(16).padStart(6, '0')}"
}

fun main() {
    // Using enum
    val direction = Direction.NORTH
    println("Direction: $direction")
    
    // Enum properties and methods
    val red = Color.RED
    println("Red RGB: ${red.rgb}")
    println("Red Hex: ${red.getHexString()}")
    
    // Enum iteration
    println("All colors:")
    for (color in Color.values()) {
        println("${color.name}: ${color.getHexString()}")
    }
}

Advanced Enum Features

enum class Planet(val mass: Double, val radius: Double) {
    MERCURY(3.303e+23, 2.4397e6),
    VENUS(4.869e+24, 6.0518e6),
    EARTH(5.976e+24, 6.37814e6),
    MARS(6.421e+23, 3.3972e6);
    
    // Methods in enum class
    fun surfaceGravity(): Double {
        val G = 6.67300E-11
        return G * mass / (radius * radius)
    }
    
    fun surfaceWeight(otherMass: Double): Double {
        return otherMass * surfaceGravity()
    }
    
    companion object {
        fun findByMass(mass: Double): Planet? {
            return values().find { it.mass == mass }
        }
    }
}

fun main() {
    val earthWeight = 175.0
    val mass = earthWeight / Planet.EARTH.surfaceGravity()
    
    println("Weight on different planets:")
    for (planet in Planet.values()) {
        val weight = planet.surfaceWeight(mass)
        println("${planet.name}: %.2f".format(weight))
    }
}

Sealed Classes

Sealed Class Definition

// Sealed class represents a restricted class hierarchy
sealed class Result<out T>

data class Success<T>(val data: T) : Result<T>()
data class Error(val exception: Throwable) : Result<Nothing>()
object Loading : Result<Nothing>()

// Using sealed class
fun handleResult(result: Result<String>) {
    when (result) {
        is Success -> println("Success: ${result.data}")
        is Error -> println("Error: ${result.exception.message}")
        Loading -> println("Loading...")
        // No else branch needed, compiler knows all possible subclasses
    }
}

fun main() {
    val results = listOf(
        Success("Data loaded successfully"),
        Error(RuntimeException("Network error")),
        Loading
    )
    
    results.forEach { handleResult(it) }
}

Sealed Interfaces

sealed interface UIEvent

data class ButtonClick(val buttonId: String) : UIEvent
data class TextInput(val text: String) : UIEvent
object ScreenLoad : UIEvent

class UIEventHandler {
    fun handle(event: UIEvent) {
        when (event) {
            is ButtonClick -> println("Button ${event.buttonId} clicked")
            is TextInput -> println("Text entered: ${event.text}")
            ScreenLoad -> println("Screen loaded")
        }
    }
}

Data Classes

Data Class Definition

// Data class automatically generates equals, hashCode, toString, copy, etc.
data class Person(
    val name: String,
    val age: Int,
    val email: String
) {
    // Data class can also have custom methods
    fun isAdult(): Boolean = age >= 18
    
    // Data class can have secondary constructors
    constructor(name: String, age: Int) : this(name, age, "")
}

fun main() {
    val person1 = Person("Alice", 25, "alice@example.com")
    val person2 = Person("Alice", 25, "alice@example.com")
    val person3 = person1.copy(age = 26)  // Copy with modification
    
    // Auto-generated methods
    println("person1 == person2: ${person1 == person2}")  // true
    println("person1: $person1")  // Auto-generated toString
    
    // Destructuring declaration
    val (name, age, email) = person1
    println("Name: $name, Age: $age, Email: $email")
    
    // Custom method
    println("Is adult: ${person1.isAdult()}")
}

Inline Classes (Value Classes)

// Inline class provides type-safe wrapper with no runtime overhead
@JvmInline
value class UserId(val value: String)

@JvmInline
value class Email(val value: String) {
    init {
        require(value.contains("@")) { "Invalid email format" }
    }
    
    fun getDomain(): String = value.substringAfter("@")
}

fun processUser(userId: UserId, email: Email) {
    println("Processing user ${userId.value} with email ${email.value}")
    println("Email domain: ${email.getDomain()}")
}

fun main() {
    val userId = UserId("user123")
    val email = Email("user@example.com")
    
    processUser(userId, email)
    
    // Type safety: cannot mix up parameters
    // processUser(email, userId)  // Compilation error
}

Nested and Inner Classes

Nested Classes

class Outer {
    private val outerProperty = "Outer property"
    
    // Nested class (static)
    class Nested {
        fun doSomething() {
            println("Nested class method")
            // println(outerProperty)  // Compilation error: cannot access outer class members
        }
    }
    
    // Inner class
    inner class Inner {
        fun doSomething() {
            println("Inner class method")
            println("Accessing: $outerProperty")  // Can access outer class members
        }
    }
}

fun main() {
    // Create nested class instance
    val nested = Outer.Nested()
    nested.doSomething()
    
    // Create inner class instance
    val outer = Outer()
    val inner = outer.Inner()
    inner.doSomething()
}

Extensions

Extension Functions

// Add extension function to String class
fun String.isPalindrome(): Boolean {
    val cleaned = this.lowercase().replace(" ", "")
    return cleaned == cleaned.reversed()
}

// Add extension function to List
fun <T> List<T>.secondOrNull(): T? = if (size >= 2) this[1] else null

// Extension property
val String.wordCount: Int
    get() = this.split("\\s+".toRegex()).size

fun main() {
    val text = "A man a plan a canal Panama"
    println("'$text' is palindrome: ${text.isPalindrome()}")
    println("Word count: ${text.wordCount}")
    
    val numbers = listOf(1, 2, 3, 4, 5)
    println("Second element: ${numbers.secondOrNull()}")
}

Extension Scope

class Host(val hostname: String) {
    fun printHostname() { print(hostname) }
}

class Connection(val host: Host, val port: Int) {
    fun printPort() { print(port) }
    
    fun Host.printConnectionString() {
        printHostname()   // Calls Host.printHostname()
        print(":")
        printPort()       // Calls Connection.printPort()
    }
    
    fun connect() {
        host.printConnectionString()  // Call extension function
    }
}

fun main() {
    Connection(Host("kotl.in"), 443).connect()
}

Delegation

Class Delegation

interface Base {
    fun print()
    fun printMessage(message: String)
}

class BaseImpl(val x: Int) : Base {
    override fun print() { println("BaseImpl: $x") }
    override fun printMessage(message: String) { println("BaseImpl: $message") }
}

// Delegate to another object
class Derived(b: Base) : Base by b {
    // Can override delegated methods
    override fun printMessage(message: String) {
        println("Derived: $message")
    }
}

fun main() {
    val base = BaseImpl(42)
    val derived = Derived(base)
    
    derived.print()  // Delegates to BaseImpl
    derived.printMessage("Hello")  // Uses overridden method
}

Best Practices

1. Package Organization

// Organize packages by feature, not technical layer
// Good organization
com.example.ecommerce/
├── user/
├── product/
├── order/
└── payment/

// Avoid this organization
com.example.ecommerce/
├── controllers/
├── services/
├── repositories/
└── models/

2. Class Design

// Prefer data classes
data class User(val id: Long, val name: String, val email: String)

// Use sealed classes for state
sealed class LoadingState {
    object Loading : LoadingState()
    data class Success(val data: String) : LoadingState()
    data class Error(val message: String) : LoadingState()
}

// Use object declarations for singletons
object ConfigManager {
    fun getConfig(key: String): String? = TODO()
}

3. Visibility

class ApiClient {
    // Expose necessary interfaces
    fun fetchData(): String = processRequest()
    
    // Hide implementation details
    private fun processRequest(): String = TODO()
    
    // Use internal to limit access within module
    internal fun debugInfo(): String = TODO()
}

Next Steps

After understanding program structure, let's dive into Kotlin's data type system.

Next Chapter: Data Types

Exercises

  1. Create a project structure with multiple packages
  2. Implement a shape drawing system using interfaces and implementation classes
  3. Design a network request result using sealed classes to represent different states
  4. Create a factory pattern example using companion objects
  5. Implement a utility library that enhances existing classes using extension functions