Inheritance Basics

When to Use

  • When you need to create a new class that is a specialized version of an existing class
  • When you want to reuse code from an existing class
  • When you need to create a hierarchy of related classes
  • When you want to extend functionality without modifying the original class
Component What It Does Example
Open Class Makes a class inheritable open class ParentClass { }
Subclass Declaration Creates a class that inherits from another class ChildClass : ParentClass() { }
Super Keyword Refers to the parent class super.methodName()
Constructor Call Initializes the parent class constructor() : super()

Practical Examples

// ===========================================
// BASIC INHERITANCE EXAMPLE
// ===========================================

// Step 1: Create a base class (parent class)
// The 'open' keyword makes this class inheritable
open class Animal {
    // Properties that all animals will have
    var name: String = ""
    var age: Int = 0
    
    // Constructor - called when creating an Animal object
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
    
    // Method that can be overridden by subclasses
    open fun makeSound() {
        println("Some animal sound")
    }
    
    // Method to display animal info
    fun displayInfo() {
        println("Name: $name, Age: $age")
    }
}

// Step 2: Create a subclass (child class)
// The colon ':' means "inherits from"
// Dog inherits all properties and methods from Animal
class Dog : Animal {
    // Additional property specific to dogs
    var breed: String = ""
    
    // Constructor for Dog - must call parent constructor
    constructor(name: String, age: Int, breed: String) : super(name, age) {
        this.breed = breed
    }
    
    // Override the makeSound method from Animal
    override fun makeSound() {
        println("Woof! Woof!")
    }
    
    // New method specific to dogs
    fun wagTail() {
        println("$name is wagging their tail!")
    }
}

// ===========================================
// INHERITANCE WITH CONSTRUCTORS EXAMPLE
// ===========================================

// Base class for vehicles
open class Vehicle(val brand: String) {
    // Properties
    var isRunning: Boolean = false
    var currentSpeed: Int = 0
    
    // Method that can be overridden
    open fun start() {
        isRunning = true
        println("Starting $brand vehicle")
    }
    
    // Method to stop the vehicle
    fun stop() {
        isRunning = false
        currentSpeed = 0
        println("Stopping $brand vehicle")
    }
    
    // Method to accelerate
    open fun accelerate(speed: Int) {
        if (isRunning) {
            currentSpeed += speed
            println("$brand vehicle accelerating to $currentSpeed mph")
        } else {
            println("Cannot accelerate - vehicle is not running!")
        }
    }
}

// Subclass for cars - inherits from Vehicle
class Car(brand: String, val model: String) : Vehicle(brand) {
    // Additional properties specific to cars
    var numberOfDoors: Int = 4
    var fuelType: String = "Gasoline"
    
    // Override the start method to add car-specific behavior
    override fun start() {
        super.start()  // Call the parent class start method first
        println("Starting $model car with $numberOfDoors doors")
    }
    
    // Override accelerate method for car-specific behavior
    override fun accelerate(speed: Int) {
        super.accelerate(speed)  // Call parent method
        if (currentSpeed > 80) {
            println("Warning: $model is going fast!")
        }
    }
    
    // New method specific to cars
    fun honkHorn() {
        println("$model car honking horn: Beep! Beep!")
    }
}

// ===========================================
// USING THE CLASSES EXAMPLE
// ===========================================

// Create and use Animal and Dog objects
println("=== ANIMAL INHERITANCE EXAMPLE ===")
val genericAnimal = Animal("Unknown", 5)
genericAnimal.makeSound()  // Outputs: Some animal sound
genericAnimal.displayInfo() // Outputs: Name: Unknown, Age: 5

val myDog = Dog("Buddy", 3, "Golden Retriever")
myDog.makeSound()    // Outputs: Woof! Woof! (overridden method)
myDog.displayInfo()  // Outputs: Name: Buddy, Age: 3 (inherited method)
myDog.wagTail()      // Outputs: Buddy is wagging their tail! (new method)

println("\n=== VEHICLE INHERITANCE EXAMPLE ===")
val genericVehicle = Vehicle("Generic")
genericVehicle.start()      // Outputs: Starting Generic vehicle
genericVehicle.accelerate(30) // Outputs: Generic vehicle accelerating to 30 mph

val myCar = Car("Toyota", "Camry")
myCar.start()        // Outputs: Starting Toyota vehicle, Starting Camry car with 4 doors
myCar.accelerate(50) // Outputs: Toyota vehicle accelerating to 50 mph
myCar.accelerate(40) // Outputs: Toyota vehicle accelerating to 90 mph, Warning: Camry is going fast!
myCar.honkHorn()     // Outputs: Camry car honking horn: Beep! Beep!

Method Overriding

When to Use

  • When you need to customize behavior inherited from a parent class
  • When you want to extend functionality of a parent method
  • When you need to provide a different implementation for a method
Component What It Does When to Use It
Open Method Makes a method overridable When you want to allow subclasses to customize behavior
Override Keyword Indicates a method is overriding a parent method When implementing a custom version of a parent method
Super Call Calls the parent class method When you want to extend rather than replace parent behavior

Practical Examples

// ===========================================
// METHOD OVERRIDING WITH BANK ACCOUNTS
// ===========================================

// Base class for all bank accounts
open class BankAccount(val accountNumber: Int, var balance: Double) {
    // Properties
    val accountType: String = "Basic Account"
    var isActive: Boolean = true
    
    // Method that can be overridden
    open fun displayBalance() {
        println("Account $accountNumber ($accountType): Balance is $${String.format("%.2f", balance)}")
    }
    
    // Method to deposit money
    open fun deposit(amount: Double) {
        if (amount > 0 && isActive) {
            balance += amount
            println("Deposited $${String.format("%.2f", amount)} to account $accountNumber")
            println("New balance: $${String.format("%.2f", balance)}")
        } else {
            println("Invalid deposit amount or account inactive")
        }
    }
    
    // Method to withdraw money
    open fun withdraw(amount: Double): Boolean {
        if (amount > 0 && amount <= balance && isActive) {
            balance -= amount
            println("Withdrew $${String.format("%.2f", amount)} from account $accountNumber")
            println("New balance: $${String.format("%.2f", balance)}")
            return true
        } else {
            println("Insufficient funds, invalid amount, or account inactive")
            return false
        }
    }
}

// Subclass for savings accounts
class SavingsAccount(accountNumber: Int, balance: Double, val interestRate: Double) 
    : BankAccount(accountNumber, balance) {
    
    // Override the accountType property
    override val accountType: String = "Savings Account"
    
    // Override displayBalance to show additional info
    override fun displayBalance() {
        super.displayBalance()  // Call parent method first to show basic info
        println("  Interest Rate: ${(interestRate * 100)}%")  // Add savings-specific info
        println("  Interest earned this period: $${String.format("%.2f", calculateInterest())}")
    }
    
    // Override deposit to add interest calculation
    override fun deposit(amount: Double) {
        super.deposit(amount)  // Use parent method for basic deposit
        // Add interest calculation for savings
        val interest = calculateInterest()
        if (interest > 0) {
            println("  Interest added: $${String.format("%.2f", interest)}")
        }
    }
    
    // Override withdraw to add savings-specific logic
    override fun withdraw(amount: Double): Boolean {
        // Check if withdrawal would leave minimum balance
        if (balance - amount < 100) {
            println("Warning: This withdrawal would leave less than $100 minimum balance")
        }
        
        // Use parent method for actual withdrawal
        return super.withdraw(amount)
    }
    
    // New method specific to savings accounts
    fun calculateInterest(): Double {
        return balance * interestRate
    }
    
    // Method to add interest to balance
    fun addInterest() {
        val interest = calculateInterest()
        balance += interest
        println("Interest of $${String.format("%.2f", interest)} added to savings account")
    }
}

// Subclass for checking accounts
class CheckingAccount(accountNumber: Int, balance: Double, val monthlyFee: Double) 
    : BankAccount(accountNumber, balance) {
    
    override val accountType: String = "Checking Account"
    
    // Override displayBalance to show fees
    override fun displayBalance() {
        super.displayBalance()  // Call parent method
        println("  Monthly fee: $${String.format("%.2f", monthlyFee)}")
    }
    
    // Override withdraw to add checking-specific logic
    override fun withdraw(amount: Double): Boolean {
        // Check if withdrawal would cause overdraft
        if (amount > balance) {
            println("Warning: This withdrawal will cause overdraft!")
        }
        
        return super.withdraw(amount)
    }
    
    // New method to apply monthly fee
    fun applyMonthlyFee() {
        balance -= monthlyFee
        println("Monthly fee of $${String.format("%.2f", monthlyFee)} applied to checking account")
    }
}

// ===========================================
// USING THE OVERRIDDEN METHODS
// ===========================================

println("=== BANK ACCOUNT OVERRIDING EXAMPLE ===")

// Create different types of accounts
val basicAccount = BankAccount(1001, 500.0)
val savingsAccount = SavingsAccount(1002, 1000.0, 0.025)  // 2.5% interest
val checkingAccount = CheckingAccount(1003, 750.0, 5.0)   // $5 monthly fee

// Test basic account
println("--- Basic Account ---")
basicAccount.displayBalance()
basicAccount.deposit(200.0)
basicAccount.withdraw(100.0)

// Test savings account with overridden methods
println("\n--- Savings Account ---")
savingsAccount.displayBalance()  // Shows interest rate and earned interest
savingsAccount.deposit(500.0)   // Shows interest calculation
savingsAccount.addInterest()     // Adds interest to balance
savingsAccount.displayBalance()  // Shows updated balance with interest

// Test checking account with overridden methods
println("\n--- Checking Account ---")
checkingAccount.displayBalance() // Shows monthly fee
checkingAccount.withdraw(800.0) // Shows overdraft warning
checkingAccount.applyMonthlyFee() // Applies monthly fee
checkingAccount.displayBalance()  // Shows balance after fee

Interfaces

When to Use

  • When you need to define a contract that multiple classes can implement
  • When you want to ensure certain methods are available in a class
  • When you need to support multiple inheritance of behavior
Feature What It Does When to Use It
Interface Declaration Defines a contract of methods When you need to define common behavior
Interface Implementation Makes a class implement an interface When you want to ensure certain methods are available
Multiple Interfaces Allows a class to implement multiple interfaces When you need to combine different behaviors

Practical Examples

// ===========================================
// INTERFACE BASICS EXAMPLE
// ===========================================

// Interface defines what methods a class MUST have
// Think of it as a "contract" or "promise"
interface Drivable {
    // Any class that implements Drivable MUST have these methods
    fun drive()
    fun stop()
    fun getSpeed(): Int
}

interface Flyable {
    fun fly()
    fun land()
    fun getAltitude(): Int
}

interface Swimmable {
    fun swim()
    fun dive()
    fun getDepth(): Int
}

// ===========================================
// IMPLEMENTING SINGLE INTERFACE
// ===========================================

// Car implements only the Drivable interface
class Car : Drivable {
    private var currentSpeed: Int = 0
    private var isEngineRunning: Boolean = false
    
    // MUST implement all methods from Drivable interface
    override fun drive() {
        if (isEngineRunning) {
            currentSpeed = 60
            println("Car is driving at $currentSpeed mph")
        } else {
            println("Cannot drive - engine is not running!")
        }
    }
    
    override fun stop() {
        currentSpeed = 0
        println("Car has stopped")
    }
    
    override fun getSpeed(): Int {
        return currentSpeed
    }
    
    // Additional methods specific to cars
    fun startEngine() {
        isEngineRunning = true
        println("Car engine started")
    }
    
    fun turnOffEngine() {
        isEngineRunning = false
        currentSpeed = 0
        println("Car engine turned off")
    }
}

// ===========================================
// IMPLEMENTING MULTIPLE INTERFACES
// ===========================================

// Duck can implement multiple interfaces
// This gives it the behavior of multiple different types
class Duck : Drivable, Flyable, Swimmable {
    private var currentSpeed: Int = 0
    private var currentAltitude: Int = 0
    private var currentDepth: Int = 0
    private var isFlying: Boolean = false
    private var isSwimming: Boolean = false
    
    // Implement Drivable interface methods
    override fun drive() {
        // Ducks don't really "drive" but we can make them walk
        currentSpeed = 5
        println("Duck is waddling at $currentSpeed mph")
    }
    
    override fun stop() {
        currentSpeed = 0
        println("Duck has stopped waddling")
    }
    
    override fun getSpeed(): Int {
        return currentSpeed
    }
    
    // Implement Flyable interface methods
    override fun fly() {
        isFlying = true
        currentAltitude = 100
        println("Duck is flying at altitude $currentAltitude feet")
    }
    
    override fun land() {
        isFlying = false
        currentAltitude = 0
        println("Duck has landed")
    }
    
    override fun getAltitude(): Int {
        return currentAltitude
    }
    
    // Implement Swimmable interface methods
    override fun swim() {
        isSwimming = true
        currentDepth = 0
        println("Duck is swimming on the surface")
    }
    
    override fun dive() {
        if (isSwimming) {
            currentDepth = 10
            println("Duck is diving to depth $currentDepth feet")
        } else {
            println("Duck must be swimming before diving!")
        }
    }
    
    override fun getDepth(): Int {
        return currentDepth
    }
    
    // Additional methods specific to ducks
    fun quack() {
        println("Duck says: Quack! Quack!")
    }
    
    fun flapWings() {
        if (isFlying) {
            println("Duck is flapping wings while flying")
        } else {
            println("Duck is flapping wings on the ground")
        }
    }
}

// ===========================================
// USING INTERFACES FOR POLYMORPHISM
// ===========================================

// Function that works with ANY Drivable object
fun testDriving(vehicle: Drivable) {
    println("Testing driving capabilities...")
    vehicle.drive()
    println("Current speed: ${vehicle.getSpeed()} mph")
    vehicle.stop()
    println("---")
}

// Function that works with ANY Flyable object
fun testFlying(aircraft: Flyable) {
    println("Testing flying capabilities...")
    aircraft.fly()
    println("Current altitude: ${aircraft.getAltitude()} feet")
    aircraft.land()
    println("---")
}

// Function that works with ANY Swimmable object
fun testSwimming(swimmer: Swimmable) {
    println("Testing swimming capabilities...")
    swimmer.swim()
    swimmer.dive()
    println("Current depth: ${swimmer.getDepth()} feet")
    println("---")
}

// ===========================================
// USING THE INTERFACE-BASED CLASSES
// ===========================================

println("=== INTERFACE IMPLEMENTATION EXAMPLE ===")

// Create objects
val car = Car()
val duck = Duck()

// Test car (implements only Drivable)
println("--- Testing Car ---")
car.startEngine()
testDriving(car)  // Car can be used anywhere a Drivable is expected
car.turnOffEngine()

// Test duck (implements multiple interfaces)
println("\n--- Testing Duck ---")
duck.quack()
testDriving(duck)   // Duck can be used as a Drivable
testFlying(duck)    // Duck can be used as a Flyable
testSwimming(duck)  // Duck can be used as a Swimmable

// Show how interfaces enable polymorphism
println("\n=== POLYMORPHISM EXAMPLE ===")
val drivableObjects: List = listOf(car, duck)
val flyableObjects: List = listOf(duck)
val swimmableObjects: List = listOf(duck)

println("All drivable objects:")
for (obj in drivableObjects) {
    testDriving(obj)
}

println("All flyable objects:")
for (obj in flyableObjects) {
    testFlying(obj)
}