Share
Explore

f24 OCTOBER 29 KOTLIN Polymorphism and Sub Classing

Summary of items you need to know (at this point) regarding Polymorphism:
Class Inheritance
We can sub class from superclasses:

When we do that: All of the Super Classes data attributes and methods are available in the Sub Class.
In terms of data field subclasses: there is nothing to talk about!

In terms of METHOD subclasses:
We must address the considerations of: Polymorphism.
There are 2 formats of Poly Morphism:
Method Over Loading : Method name does not change: Method signature DOES change in method overloading: we can change the number of and type input parameters to the method.
Method Over Riding : Change what the method is doing : the algorithm, what the method is doing.
Study our examples until you develop an intuition on when it is the right time to use Overloading or Overriding. (In practice your UML diagram will tell you where to go with this.).

In Monday's session, all topics from the plan were covered:

What is inheritance? Why is inheritance needed? How is inheritance implemented? Adding properties to a child class Adding properties to a parent class Passing initial property values to parent using super() in the child class constructor

Here is some Object Oriented Programming Theory Concepts to assist us in navigating today’s Labs.

Today → Class Plan for Tuesday October 29:

Quiz 5 will be posted at 8 am WEDNESDAY. It will be due at 10:00 pm

Today’s Topics:

Polymorphism

Exception Handling

Interfaces

megaphone

This lab workbook provides a comprehensive introduction to polymorphism in Kotlin, starting with basic concepts and building up to complex real-world examples.

The e-commerce system example demonstrates both types of polymorphism in a practical context that students can relate to.


Lab Exercise: Understanding Polymorphism in Kotlin
info
Introduction to polymorphism using a horticultural example.

```kotlin // Botanical Gardens Management System // Demonstrating how polymorphism naturally models real-world hierarchies
// Base class representing fundamental plant characteristics open class Plant( val scientificName: String, val commonName: String, protected var heightInCm: Double, protected var healthStatus: String ) { open fun water() { println("Basic watering procedure for $commonName") } open fun prune() { println("Basic pruning procedure for $commonName") } open fun fertilize() { println("Basic fertilizing procedure for $commonName") } open fun displayCareInstructions() { println("Basic care instructions for $commonName ($scientificName)") } }
// Specialized class for tropical plants class TropicalPlant( scientificName: String, commonName: String, heightInCm: Double, healthStatus: String, private val humidityRequired: Int, private val minimumTemp: Double ) : Plant(scientificName, commonName, heightInCm, healthStatus) { override fun water() { println("Misting $commonName to maintain $humidityRequired% humidity") println("Ensuring soil remains consistently moist but not waterlogged") } override fun displayCareInstructions() { println("=== Tropical Plant Care: $commonName ===") println("Scientific Name: $scientificName") println("Required Humidity: $humidityRequired%") println("Minimum Temperature: $minimumTemp°C") println("Needs regular misting and humid conditions") } }
// Specialized class for desert plants class DesertPlant( scientificName: String, commonName: String, heightInCm: Double, healthStatus: String, private val droughtTolerance: String, private val sandContent: Int ) : Plant(scientificName, commonName, heightInCm, healthStatus) { override fun water() { println("Minimal watering for $commonName - checking soil dryness first") println("Using specialized desert plant watering technique") } override fun displayCareInstructions() { println("=== Desert Plant Care: $commonName ===") println("Scientific Name: $scientificName") println("Drought Tolerance: $droughtTolerance") println("Required Sand Content: $sandContent%") println("Allow soil to dry completely between waterings") } }
// Specialized class for alpine plants class AlpineFlora( scientificName: String, commonName: String, heightInCm: Double, healthStatus: String, private val minimumAltitude: Int, private val frostResistant: Boolean ) : Plant(scientificName, commonName, heightInCm, healthStatus) { override fun water() { println("Careful watering for $commonName - ensuring good drainage") println("Using specialized alpine watering technique to prevent root rot") } override fun displayCareInstructions() { println("=== Alpine Plant Care: $commonName ===") println("Scientific Name: $scientificName") println("Minimum Altitude: ${minimumAltitude}m") println("Frost Resistant: $frostResistant") println("Requires excellent drainage and protection from excessive moisture") } }
// Horticulturist class demonstrating polymorphic plant care class Horticulturist(val name: String, val specialization: String) { fun providePlantCare(plant: Plant) { println("\n$name ($specialization) is tending to the plant:") plant.displayCareInstructions() plant.water() plant.prune() plant.fertilize() } }
// Botanical Garden management demonstrating the power of polymorphism class BotanicalGarden { private val plants = mutableListOf<Plant>() private val horticulturists = mutableListOf<Horticulturist>() fun addPlant(plant: Plant) { plants.add(plant) } fun addHorticulturist(horticulturist: Horticulturist) { horticulturists.add(horticulturist) } fun performDailyCare() { println("\n=== Daily Care Routine Starting ===\n") plants.forEach { plant -> // Find appropriate specialist for the plant type val specialist = when (plant) { is TropicalPlant -> horticulturists.find { it.specialization == "Tropical" } is DesertPlant -> horticulturists.find { it.specialization == "Desert" } is AlpineFlora -> horticulturists.find { it.specialization == "Alpine" } else -> horticulturists.firstOrNull() } specialist?.providePlantCare(plant) ?: println("No specialist available for this plant type") } } }
fun main() { println(""" Welcome to the Polymorphic Botanical Gardens! In our gardens, we demonstrate how object-oriented programming naturally mirrors the real world. Just as plants have evolved to thrive in different environments, our code evolves to handle different types of plants efficiently. Watch as our specialized horticulturists care for various plants: """.trimIndent()) val garden = BotanicalGarden() // Adding different types of plants garden.addPlant(TropicalPlant( "Strelitzia reginae", "Bird of Paradise", 150.0, "Healthy", 80, 18.0 )) garden.addPlant(DesertPlant( "Ferocactus wislizeni", "Fishhook Barrel Cactus", 60.0, "Excellent", "High", 90 )) garden.addPlant(AlpineFlora( "Leontopodium alpinum", "Edelweiss", 15.0, "Good", 2000, true )) // Adding specialized horticulturists garden.addHorticulturist(Horticulturist("Maria", "Tropical")) garden.addHorticulturist(Horticulturist("Ahmed", "Desert")) garden.addHorticulturist(Horticulturist("Hans", "Alpine")) // Perform daily care routine garden.performDailyCare() }
```
This example demonstrates several key benefits of polymorphism:
1. **Natural Modeling**: Just as real plants share common characteristics but require specialized care, our Plant hierarchy models this through inheritance and polymorphism.
2. **Code Reusability**: The base Plant class provides common functionality that specialized plants can inherit and modify as needed.
3. **Extensibility**: New plant types can be easily added by creating new classes that inherit from Plant, without changing existing code.
4. **Simplified Management**: The BotanicalGarden class can treat all plants uniformly through their common interface while still providing specialized care.
5. **Type Safety**: The compiler ensures that all plant types implement required functionality, preventing errors.
The example shows how polymorphism allows us to: - Write code that works with objects at different levels of abstraction - Handle specialized cases without complex conditional logic - Create maintainable and scalable systems - Model real-world relationships effectively
Challenge thinking for students:
1. Add more specialized plant types with unique care requirements
2. Include additional garden management features
3. Add more complex interactions between plants and horticulturists

Learning Objectives
By the end of this lab, students will be able to:
1. Understand the fundamental concepts of objects, types, and classes in Kotlin
2. Explain and implement polymorphism in Kotlin applications
3. Differentiate between method overloading and method overriding
4. Create flexible and reusable code using polymorphic principles
5. Apply polymorphism to solve real-world business problems

Part 1: Foundation Concepts

Objects, Types, and Classes
In Kotlin, everything starts with objects. An object is a self-contained unit that contains: - Data (properties) - Behaviors (methods) - A specific type (class)
```kotlin // Basic class definition class Smartphone { var brand: String = "" var model: String = "" fun makeCall() { println("Calling from $brand $model") } }
// Creating an object val myPhone = Smartphone() ```

Part 2: Understanding Polymorphism


Software systems are constructed by connecting OBJECTS together:
By composition
By inheritance.

Polymorphism belongs to the side of this equation which relates to inheritance.
Inheritance means to “sub class” (create a small scale version of a parent class).
In the subclass, we can access the parent’s data fields and methods.
In general, we won’t know at design time how the sub class might be used.
Therefore the programmers is responsible for provide situationally appropriate method implementations for the subclasses.


Polymorphism means "many forms" and allows objects to take on multiple forms while sharing common characteristics.
In Kotlin, we have two main types of polymorphism:
1. Static Polymorphism (Compile-time) - Method Overloading
2. Dynamic Polymorphism (Runtime) - Method Overriding

Exercise 1: Method Overloading

Method overloading allows multiple methods with the same name but different parameters.
```kotlin class Calculator { // Add two numbers fun add(a: Int, b: Int): Int { return a + b } // Add three numbers fun add(a: Int, b: Int, c: Int): Int { return a + b + c } // Add two doubles fun add(a: Double, b: Double): Double { return a + b } }
// Usage val calc = Calculator() println(calc.add(5, 3)) // Calls first method println(calc.add(5, 3, 2)) // Calls second method println(calc.add(5.5, 3.2)) // Calls third method ```

Exercise 2: Method Overriding

Method overriding allows a subclass to provide a specific implementation of a method already defined in its parent class.
open class Vehicle { open fun startEngine() { println("Generic vehicle engine starting") } open fun stopEngine() { println("Engine stopping") } }
class ElectricCar : Vehicle() { override fun startEngine() { println("Electric motor powering up silently") } override fun stopEngine() { println("Electric motor shutting down") } fun chargeBattery() { println("Charging battery...") } }
class DieselTruck : Vehicle() { override fun startEngine() { println("Diesel engine starting with a rumble") } override fun stopEngine() { println("Diesel engine cooling down and stopping") } fun loadCargo() { println("Loading cargo into truck") } }
// Fleet management class to demonstrate polymorphic collections class FleetManager { private val vehicles = mutableListOf<Vehicle>() fun addVehicle(vehicle: Vehicle) { vehicles.add(vehicle) } fun startAllVehicles() { println("\nStarting all vehicles in fleet:") vehicles.forEach { it.startEngine() } } fun stopAllVehicles() { println("\nStopping all vehicles in fleet:") vehicles.forEach { it.stopEngine() } } fun performSpecializedTasks() { println("\nPerforming specialized tasks:") vehicles.forEach { vehicle -> when (vehicle) { is ElectricCar -> vehicle.chargeBattery() is DieselTruck -> vehicle.loadCargo() else -> println("No specialized task for this vehicle type") } } } }
fun main() { println("Vehicle Polymorphism Demo") println("-------------------------") // Creating vehicles val genericVehicle = Vehicle() val tesla = ElectricCar() val volvoTruck = DieselTruck() // Demonstration 1: Direct usage println("\nDirect vehicle usage:") println("Generic Vehicle:") genericVehicle.startEngine() println("\nElectric Car:") tesla.startEngine() tesla.chargeBattery() println("\nDiesel Truck:") volvoTruck.startEngine() volvoTruck.loadCargo() // Demonstration 2: Polymorphic array println("\nPolymorphic array demonstration:") val vehicles = arrayOf(genericVehicle, tesla, volvoTruck) vehicles.forEach { it.startEngine() } // Demonstration 3: Fleet management val fleet = FleetManager() fleet.addVehicle(genericVehicle) fleet.addVehicle(tesla) fleet.addVehicle(volvoTruck) fleet.startAllVehicles() fleet.performSpecializedTasks() fleet.stopAllVehicles() // Demonstration 4: Safe casting and type checking println("\nType checking and safe casting demonstration:") vehicles.forEach { vehicle -> when (vehicle) { is ElectricCar -> { println("Found Electric Car:") vehicle.chargeBattery() } is DieselTruck -> { println("Found Diesel Truck:") vehicle.loadCargo() } else -> println("Found generic vehicle") } } // Demonstration 5: Polymorphic function parameter fun processVehicle(vehicle: Vehicle) { println("\nProcessing vehicle:") vehicle.startEngine() if (vehicle is ElectricCar) { println("Processing electric car specifically:") vehicle.chargeBattery() } } println("\nTesting polymorphic function:") processVehicle(tesla) processVehicle(volvoTruck) }
info
Vehicle Polymorphism Demo -------------------------
Direct vehicle usage: Generic Vehicle: Generic vehicle engine starting
Electric Car: Electric motor powering up silently Charging battery...
Diesel Truck: Diesel engine starting with a rumble Loading cargo into truck
Polymorphic array demonstration: Generic vehicle engine starting Electric motor powering up silently Diesel engine starting with a rumble
Starting all vehicles in fleet: Generic vehicle engine starting Electric motor powering up silently Diesel engine starting with a rumble
Performing specialized tasks: No specialized task for this vehicle type Charging battery... Loading cargo into truck
Stopping all vehicles in fleet: Engine stopping Electric motor shutting down Diesel engine cooling down and stopping
Type checking and safe casting demonstration: Found generic vehicle Found Electric Car: Charging battery... Found Diesel Truck: Loading cargo into truck
Testing polymorphic function:
Processing vehicle: Electric motor powering up silently Processing electric car specifically: Charging battery...
Processing vehicle: Diesel engine starting with a rumble
Process finished with exit code 0

Part 3: Real-World Application - E-Commerce System

Let's create a more complex example modeling an e-commerce system:
// Base Product class open class Product( val id: String, val name: String, open val price: Double ) { open fun calculateTax(): Double { return price * 0.1 // Basic 10% tax } open fun display() { println("Product: $name - $${price}") } }
// Digital Product class DigitalProduct( id: String, name: String, price: Double, val downloadSize: Double ) : Product(id, name, price) { override fun calculateTax(): Double { return price * 0.05 // Digital goods taxed at 5% } override fun display() { println("Digital Product: $name - $${price.format()} (${downloadSize}GB)") } }
// Physical Product class PhysicalProduct( id: String, name: String, price: Double, val weight: Double, val dimensions: String ) : Product(id, name, price) { override fun calculateTax(): Double { return price * 0.15 // Physical goods taxed at 15% } override fun display() { println("Physical Product: $name - $${price.format()} (${weight}kg)") } // Overloaded method for shipping calculation fun calculateShipping(distance: Int): Double { return weight * 0.1 * distance } fun calculateShipping(distance: Int, express: Boolean): Double { val baseShipping = calculateShipping(distance) return if (express) baseShipping * 1.5 else baseShipping } }
// Subscription Product class SubscriptionProduct( id: String, name: String, private val monthlyPrice: Double, private val durationMonths: Int ) : Product(id, name, monthlyPrice * durationMonths) { override fun calculateTax(): Double { return price * 0.08 // Subscription goods taxed at 8% } override fun display() { println("Subscription: $name - $${monthlyPrice.format()}/month for $durationMonths months (Total: $${price.format()})") } }
// Shopping Cart demonstrating polymorphism class ShoppingCart { private val items = mutableListOf<Product>() fun addItem(product: Product) { items.add(product) } fun removeItem(product: Product) { items.remove(product) } fun calculateTotal(): Double { return items.sumOf { it.price + it.calculateTax() } } fun calculateTotalTax(): Double { return items.sumOf { it.calculateTax() } } fun displayCart() { if (items.isEmpty()) { println("Shopping Cart is empty") return } println("\nShopping Cart Contents:") println("----------------------") items.forEach { it.display() } println("----------------------") println("Subtotal: $${items.sumOf { it.price }.format()}") println("Total Tax: $${calculateTotalTax().format()}") println("Final Total: $${calculateTotal().format()}") } }
// Extension function to format currency fun Double.format(): String = String.format("%.2f", this)
// Order processor to demonstrate polymorphic handling class OrderProcessor { fun processOrder(cart: ShoppingCart, shippingAddress: String? = null) { println("\nProcessing Order:") println("----------------") cart.displayCart() if (shippingAddress != null) { println("\nShipping to: $shippingAddress") } println("\nOrder processed successfully!") } }
fun main() { println("E-Commerce System Demonstration") println("==============================") // Create sample products val ebook = DigitalProduct("D1", "Programming Kotlin", 29.99, 2.5) val physicalBook = PhysicalProduct("P1", "Kotlin in Action", 49.99, 0.8, "24x18x3cm") val laptop = PhysicalProduct("P2", "MacBook Pro", 1299.99, 2.1, "31x22x1.5cm") val streaming = SubscriptionProduct("S1", "Video Streaming", 14.99, 12) val software = SubscriptionProduct("S2", "Cloud Storage", 9.99, 6) // Create shopping cart val cart = ShoppingCart() // Demonstration 1: Adding items and displaying cart println("\nAdding items to cart...") cart.addItem(ebook) cart.addItem(physicalBook) cart.displayCart() // Demonstration 2: Calculating shipping for physical items println("\nShipping Calculations:") println("Standard shipping for '${physicalBook.name}': $${physicalBook.calculateShipping(100).format()}") println("Express shipping for '${physicalBook.name}': $${physicalBook.calculateShipping(100, true).format()}") // Demonstration 3: Adding subscription products println("\nAdding subscription products...") cart.addItem(streaming) cart.addItem(software) cart.displayCart() // Demonstration 4: Order processing val orderProcessor = OrderProcessor() orderProcessor.processOrder(cart, "123 Main St, Anytown, USA") // Demonstration 5: Creating a digital-only cart println("\nCreating a digital-only cart...") val digitalCart = ShoppingCart() digitalCart.addItem(ebook) digitalCart.addItem(streaming) digitalCart.addItem(software) println("\nProcessing digital order...") orderProcessor.processOrder(digitalCart) // Demonstration 6: Polymorphic type checking println("\nAnalyzing cart contents:") val allProducts = listOf(ebook, physicalBook, laptop, streaming, software) println("\nProduct Analysis:") allProducts.forEach { product -> when (product) { is DigitalProduct -> println("Digital - ${product.name} (${product.downloadSize}GB)") is PhysicalProduct -> println("Physical - ${product.name} (${product.weight}kg)") is SubscriptionProduct -> println("Subscription - ${product.name}") else -> println("Unknown product type") } } }

image.png

Lab Exercise Tasks

1. done: Create a main function that demonstrates the use of the e-commerce system:
```kotlin ​fun main() { val cart = ShoppingCart() // Add different types of products cart.addItem(DigitalProduct("D1", "E-book: Kotlin Programming", 29.99, 0.5)) cart.addItem(PhysicalProduct("P1", "Mechanical Keyboard", 149.99, 1.2, "45x15x5")) // Display cart and total cart.displayCart() // Demonstrate shipping calculation with overloaded methods val keyboard = PhysicalProduct("P1", "Mechanical Keyboard", 149.99, 1.2, "45x15x5") println("Standard shipping: $${keyboard.calculateShipping(100)}") println("Express shipping: $${keyboard.calculateShipping(100, true)}") }
minus

E-Commerce System Demonstration ==============================

Adding items to cart...
Shopping Cart Contents: ---------------------- Digital Product: Programming Kotlin - $29.99 (2.5GB) Physical Product: Kotlin in Action - $49.99 (0.8kg) ---------------------- Subtotal: $79.98 Total Tax: $9.00 Final Total: $88.98
Shipping Calculations: Standard shipping for 'Kotlin in Action': $8.00 Express shipping for 'Kotlin in Action': $12.00
Adding subscription products...
Shopping Cart Contents: ---------------------- Digital Product: Programming Kotlin - $29.99 (2.5GB) Physical Product: Kotlin in Action - $49.99 (0.8kg) Subscription: Video Streaming - $14.99/month for 12 months (Total: $179.88) Subscription: Cloud Storage - $9.99/month for 6 months (Total: $59.94) ---------------------- Subtotal: $319.80 Total Tax: $28.18 Final Total: $347.98
Processing Order: ----------------
Shopping Cart Contents: ---------------------- Digital Product: Programming Kotlin - $29.99 (2.5GB) Physical Product: Kotlin in Action - $49.99 (0.8kg) Subscription: Video Streaming - $14.99/month for 12 months (Total: $179.88) Subscription: Cloud Storage - $9.99/month for 6 months (Total: $59.94) ---------------------- Subtotal: $319.80 Total Tax: $28.18 Final Total: $347.98
Shipping to: 123 Main St, Anytown, USA
Order processed successfully!
Creating a digital-only cart...
Processing digital order...
Processing Order: ----------------
Shopping Cart Contents: ---------------------- Digital Product: Programming Kotlin - $29.99 (2.5GB) Subscription: Video Streaming - $14.99/month for 12 months (Total: $179.88) Subscription: Cloud Storage - $9.99/month for 6 months (Total: $59.94) ---------------------- Subtotal: $269.81 Total Tax: $20.69 Final Total: $290.50
Order processed successfully!
Analyzing cart contents:
Product Analysis: Digital - Programming Kotlin (2.5GB) Physical - Kotlin in Action (0.8kg) Physical - MacBook Pro (2.1kg) Subscription - Video Streaming Subscription - Cloud Storage
Process finished with exit code 0


2. Add a new type of product called `SubscriptionProduct` that has: - A subscription duration in months - A monthly fee - Override the price calculation to return the total cost for the subscription period - Override the tax calculation to handle subscription-based pricing
megaphone
This implementation provides:
Complete SubscriptionProduct Class
Monthly fee handling
Duration tracking
Setup fee support
Auto-renewal flag
Comprehensive price and tax calculations
Enhanced Shopping Cart
Handles subscription products
Calculates monthly recurring costs
Groups items by type for display
Provides detailed summaries
Additional Features
Renewal cost estimation
Remaining months calculation
Setup fee handling
Auto-renewal tracking
Formatted currency display
Demonstration
Various subscription types
Different pricing models
Cart operations
Subscription analysis
The code demonstrates how to:
Calculate total subscription costs
Handle different subscription durations
Apply appropriate tax rates
Track recurring charges
Manage setup fees
Support auto-renewal options

// Base Product class open class Product( val id: String, val name: String, open val price: Double ) { open fun calculateTax(): Double { return price * 0.1 // Basic 10% tax } open fun display() { println("Product: $name - $${price.format()}") } }

// Subscription Product implementation class SubscriptionProduct( id: String, name: String, private val monthlyFee: Double, private val durationMonths: Int, private val setupFee: Double = 0.0, private val hasAutoRenewal: Boolean = false ) : Product(id, name, 0.0) { // Initial price set to 0, will be calculated // Override price to calculate total subscription cost override val price: Double get() = (monthlyFee * durationMonths) + setupFee // Special tax calculation for subscriptions override fun calculateTax(): Double { // Different tax rates for different components val monthlyTax = monthlyFee * durationMonths * 0.08 // 8% tax on subscription val setupTax = setupFee * 0.10 // 10% tax on setup fee return monthlyTax + setupTax } // Enhanced display method for subscription details override fun display() { println(""" |Subscription: $name |Monthly Fee: $${monthlyFee.format()} |Duration: $durationMonths months |Setup Fee: $${setupFee.format()} |Auto-Renewal: ${if (hasAutoRenewal) "Yes" else "No"} |Total Cost: $${price.format()} |Total Tax: $${calculateTax().format()} |Grand Total: $${(price + calculateTax()).format()} """.trimMargin()) } // Additional subscription-specific methods fun getRemainingMonths(): Int { // In a real application, this would compare against subscription start date return durationMonths } fun getMonthlyFee(): Double = monthlyFee fun estimateRenewalCost(): Double { return if (hasAutoRenewal) { monthlyFee * durationMonths // No setup fee on renewal } else { 0.0 } } }
// Shopping Cart to handle all product types class ShoppingCart { private val items = mutableListOf<Product>() fun addItem(product: Product) { items.add(product) } fun removeItem(product: Product) { items.remove(product) } fun calculateTotal(): Double { return items.sumOf { it.price + it.calculateTax() } } fun calculateTotalTax(): Double { return items.sumOf { it.calculateTax() } } // Calculate total monthly recurring cost fun calculateMonthlyRecurring(): Double { return items .filterIsInstance<SubscriptionProduct>() .sumOf { it.getMonthlyFee() } } fun displayCart() { if (items.isEmpty()) { println("Shopping Cart is empty") return } println("\nShopping Cart Contents:") println("======================") // Group items by type for better display val subscriptions = items.filterIsInstance<SubscriptionProduct>() val otherProducts = items.filter { it !is SubscriptionProduct } if (subscriptions.isNotEmpty()) { println("\nSubscriptions:") println("--------------") subscriptions.forEach { it.display() } } if (otherProducts.isNotEmpty()) { println("\nOther Products:") println("--------------") otherProducts.forEach { it.display() } } println("\nCart Summary:") println("-------------") println("Subtotal: $${items.sumOf { it.price }.format()}") println("Total Tax: $${calculateTotalTax().format()}") println("Final Total: $${calculateTotal().format()}") println("Monthly Recurring: $${calculateMonthlyRecurring().format()}") } }
// Utility extension function for currency formatting fun Double.format(): String = String.format("%.2f", this)
fun main() { println("Subscription Product Demonstration") println("================================") // Create various subscription products val streamingService = SubscriptionProduct( id = "SUB001", name = "Premium Streaming Service", monthlyFee = 14.99, durationMonths = 12, setupFee = 0.0, hasAutoRenewal = true ) val gymMembership = SubscriptionProduct( id = "SUB002", name = "Fitness Club Membership", monthlyFee = 49.99, durationMonths = 6, setupFee = 99.99, hasAutoRenewal = false ) val softwareLicense = SubscriptionProduct( id = "SUB003", name = "Professional Software Suite", monthlyFee = 29.99, durationMonths = 12, setupFee = 49.99, hasAutoRenewal = true ) // Create and populate shopping cart val cart = ShoppingCart() // Demonstration 1: Adding single subscription println("\nAdding streaming service to cart:") cart.addItem(streamingService) cart.displayCart() // Demonstration 2: Multiple subscriptions println("\nAdding gym membership and software license:") cart.addItem(gymMembership) cart.addItem(softwareLicense) cart.displayCart() // Demonstration 3: Analyzing subscription costs println("\nSubscription Analysis:") println("=====================") val subscriptions = listOf(streamingService, gymMembership, softwareLicense) subscriptions.forEach { subscription -> println("\n${subscription.name}:") println("- Monthly Fee: $${subscription.getMonthlyFee().format()}") println("- Total Cost: $${subscription.price.format()}") println("- Tax: $${subscription.calculateTax().format()}") println("- Renewal Cost: $${subscription.estimateRenewalCost().format()}") println("- Remaining Months: ${subscription.getRemainingMonths()}") } // Demonstration 4: Cart operations println("\nCart Operations:") println("===============") println("Monthly Recurring Total: $${cart.calculateMonthlyRecurring().format()}") println("Total Tax: $${cart.calculateTotalTax().format()}") println("Cart Total: $${cart.calculateTotal().format()}") // Demonstration 5: Removing items println("\nRemoving gym membership from cart:") cart.removeItem(gymMembership) cart.displayCart() }

3. Create a reporting system that can generate different types of reports using polymorphism: ​ - Create a base `Report` class - Create specific report types (Sales, Inventory, Customer) - Use method overriding to customize how each report is generated and displayed

error

import java.time.LocalDateTime import java.time.format.DateTimeFormatter

// Data classes for our business entities data class Product( val id: String, val name: String, val price: Double, val stockLevel: Int, val category: String )
data class Customer( val id: String, val name: String, val email: String, val joinDate: LocalDateTime, val totalPurchases: Double )
data class Sale( val id: String, val customerId: String, val productId: String, val quantity: Int, val saleDate: LocalDateTime, val totalAmount: Double )
// Base Report class abstract class Report(val title: String) { protected val dateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") // Template method pattern fun generateReport() { printHeader() printContent() printFooter() } private fun printHeader() { println("\n============================================") println("Report: $title") println("Generated: ${LocalDateTime.now().format(dateFormatter)}") println("============================================") } protected abstract fun printContent() private fun printFooter() { println("============================================") println("End of Report") println("============================================\n") } protected fun formatCurrency(amount: Double): String { return String.format("$%.2f", amount) } protected fun formatPercentage(value: Double): String { return String.format("%.1f%%", value * 100) } }
// Sales Report class SalesReport( private val sales: List<Sale>, private val products: Map<String, Product>, private val customers: Map<String, Customer> ) : Report("Sales Analysis Report") { override fun printContent() { val totalRevenue = sales.sumOf { it.totalAmount } val totalSales = sales.size val averageOrderValue = if (totalSales > 0) totalRevenue / totalSales else 0.0 println("\nSummary Statistics:") println("------------------") println("Total Revenue: ${formatCurrency(totalRevenue)}") println("Total Orders: $totalSales") println("Average Order Value: ${formatCurrency(averageOrderValue)}") // Top selling products println("\nTop Selling Products:") println("--------------------") sales.groupBy { it.productId } .mapValues { it.value.sumOf { sale -> sale.quantity } } .entries.sortedByDescending { it.value } .take(3) .forEach { entry -> val product = products[entry.key] println("${product?.name}: ${entry.value} units") } // Daily sales trend println("\nDaily Sales Trend:") println("-----------------") sales.groupBy { it.saleDate.toLocalDate() } .entries.sortedBy { it.key } .forEach { (date, dailySales) -> println("$date: ${formatCurrency(dailySales.sumOf { it.totalAmount })}") } } }
// Inventory Report class InventoryReport( private val products: List<Product> ) : Report("Inventory Status Report") { override fun printContent() { val totalItems = products.sumOf { it.stockLevel } val totalValue = products.sumOf { it.price * it.stockLevel } println("\nInventory Summary:") println("-----------------") println("Total Items in Stock: $totalItems") println("Total Inventory Value: ${formatCurrency(totalValue)}") // Stock levels by category println("\nStock Levels by Category:") println("----------------------") products.groupBy { it.category } .forEach { (category, products) -> val categoryValue = products.sumOf { it.price * it.stockLevel } println("$category:") println(" Items: ${products.sumOf { it.stockLevel }}") println(" Value: ${formatCurrency(categoryValue)}") } // Low stock alerts println("\nLow Stock Alerts (< 10 units):") println("-----------------------------") products.filter { it.stockLevel < 10 } .sortedBy { it.stockLevel } .forEach { product -> println("${product.name}: ${product.stockLevel} units remaining") } } }
// Customer Report class CustomerReport( private val customers: List<Customer>, private val sales: List<Sale> ) : Report("Customer Analysis Report") { override fun printContent() { val totalCustomers = customers.size val totalRevenue = sales.sumOf { it.totalAmount } val averageCustomerValue = if (totalCustomers > 0) totalRevenue / totalCustomers else 0.0 println("\nCustomer Base Summary:") println("---------------------") println("Total Customers: $totalCustomers") println("Average Customer Value: ${formatCurrency(averageCustomerValue)}") // Customer segments by purchase value println("\nCustomer Segments:") println("-----------------") val segments = customers.groupBy { customer -> when (customer.totalPurchases) { in 0.0..100.0 -> "Bronze" in 100.0..500.0 -> "Silver" in 500.0..1000.0 -> "Gold" else -> "Platinum" } } segments.forEach { (segment, customers) -> val percentage = customers.size.toDouble() / totalCustomers println("$segment: ${customers.size} customers (${formatPercentage(percentage)})") } // Recent customers println("\nRecent Customers (Last 30 days):") println("-------------------------------") val thirtyDaysAgo = LocalDateTime.now().minusDays(30) customers.filter { it.joinDate.isAfter(thirtyDaysAgo) } .sortedByDescending { it.joinDate } .forEach { customer -> println("${customer.name} - Joined: ${customer.joinDate.format(dateFormatter)}") } } }
// Report Generator to demonstrate polymorphic report handling class ReportGenerator { fun generateReports(vararg reports: Report) { reports.forEach { report -> report.generateReport() } } }
fun main() { // Create sample data val products = listOf( Product("P1", "Laptop", 999.99, 15, "Electronics"), Product("P2", "Smartphone", 699.99, 8, "Electronics"), Product("P3", "Headphones", 99.99, 25, "Accessories"), Product("P4", "Mouse", 29.99, 5, "Accessories"), Product("P5", "Keyboard", 59.99, 12, "Accessories") ).associateBy { it.id } val customers = listOf( Customer("C1", "John Doe", "john@example.com", LocalDateTime.now().minusMonths(2), 1500.0), Customer("C2", "Jane Smith", "jane@example.com", LocalDateTime.now().minusDays(15), 750.0), Customer("C3", "Bob Johnson", "bob@example.com", LocalDateTime.now().minusDays(5), 250.0) ).associateBy { it.id } val sales = listOf( Sale("S1", "C1", "P1", 1, LocalDateTime.now().minusDays(5), 999.99), Sale("S2", "C2", "P2", 1, LocalDateTime.now().minusDays(3), 699.99), Sale("S3", "C3", "P3", 2, LocalDateTime.now().minusDays(1), 199.98), Sale("S4", "C1", "P4", 1, LocalDateTime.now(), 29.99) ) // Create reports val salesReport = SalesReport(sales, products, customers) val inventoryReport = InventoryReport(products.values.toList()) val customerReport = CustomerReport(customers.values.toList(), sales) // Generate all reports val reportGenerator = ReportGenerator() reportGenerator.generateReports(salesReport, inventoryReport, customerReport) }
This implementation provides:
Complete Report Hierarchy
Abstract base Report class with template method pattern
Specialized report types for different business aspects
Polymorphic report generation
Rich Data Model
Products with inventory tracking
Customer information and history
Sales transactions
Category management
Different Report Types
Sales Analysis Report
Revenue metrics
Top selling products
Sales trends
Inventory Status Report
Stock levels
Category analysis
Low stock alerts
Customer Analysis Report
Customer segments
Purchase patterns
Recent customer tracking
Formatting and Presentation
Consistent report layout
Currency formatting
Percentage formatting
Date formatting
Analysis Features
Summary statistics
Trend analysis
Segmentation
Alerts and notifications

Key Concepts to Remember

1. **Method Overloading**: - Same method name, different parameters - Resolved at compile-time - Based on method signature - Cannot be based on return type alone
2. **Method Overriding**: - Same method signature in parent and child classes - Resolved at runtime - Requires `open` keyword on parent class/method - Uses `override` keyword in child class
3. **Benefits of Polymorphism**: - Code reusability - Flexibility in design - Easier maintenance - Better organization of code - Runtime dynamic behavior

Challenge Exercise (*** this is your homework exercise: Paste your screen shot in Slack)

Create a notification system for the e-commerce platform that demonstrates both types of polymorphism:
1. Create a base `Notification` class
2. Implement different notification types (Email, SMS, Push)
3. Use method overloading for different notification formats
4. Use method overriding for different delivery mechanisms

Document your solution highlighting the use of both types of polymorphism in action.

megaphone

Advanced Topics in Polymorphism:

Additional Topics for Android Development Students
1. Sealed Classes
megaphone

# When to Use Sealed Classes: A Practical Guide

## The Power of Sealed Classes
Sealed classes in Kotlin shine brightest when you need to represent a restricted set of types that are known at compile time.
Think of them as "enums on steroids" – they provide type safety and exhaustive checking while allowing each subtype to carry its own data.
## Best Use Cases
### 1. State Management ```kotlin sealed class UiState { object Loading : UiState() data class Success(val data: List<Item>) : UiState() data class Error(val message: String) : UiState() object Empty : UiState() } ``` Perfect for UI state management because: - All possible states are known - Each state can carry relevant data - Compiler ensures you handle all cases - Makes state transitions explicit and safe
### 2. Network Response Handling ```kotlin sealed class ApiResponse<out T> { data class Success<T>(val data: T) : ApiResponse<T>() data class Error( val code: Int, val message: String, val cause: Exception? = null ) : ApiResponse<Nothing>() object Loading : ApiResponse<Nothing>() data class NetworkError(val exception: IOException) : ApiResponse<Nothing>() }
class UserRepository { fun fetchUser(id: String): ApiResponse<User> { return try { // Network call ApiResponse.Success(user) } catch (e: IOException) { ApiResponse.NetworkError(e) } catch (e: Exception) { ApiResponse.Error(500, "Unknown error", e) } } } ```
### 3. Event Handling ```kotlin sealed class NavigationEvent { data class NavigateToDetail(val id: String) : NavigationEvent() object NavigateBack : NavigationEvent() data class NavigateToExternalUrl(val url: String) : NavigationEvent() data class ShowDialog(val message: String) : NavigationEvent() } ```
### 4. Domain-Specific Result Types ```kotlin sealed class PaymentResult { data class Success( val transactionId: String, val amount: Double ) : PaymentResult() sealed class Failure : PaymentResult() { data class InsufficientFunds(val missing: Double) : Failure() data class CardDeclined(val reason: String) : Failure() object NetworkError : Failure() data class SecurityCheck(val required: List<String>) : Failure() } } ```
## Why Sealed Classes Are Superior in These Cases
1. **Type Safety** ```kotlin fun handlePayment(result: PaymentResult) { when (result) { is PaymentResult.Success -> processSuccess(result.transactionId) is PaymentResult.Failure.InsufficientFunds -> requestMoreFunds(result.missing) is PaymentResult.Failure.CardDeclined -> showError(result.reason) is PaymentResult.Failure.NetworkError -> retryPayment() is PaymentResult.Failure.SecurityCheck -> initiateVerification(result.required) } // Compiler ensures all cases are handled } ```
2. **Hierarchical Organization** ```kotlin sealed class ValidationResult { sealed class Valid : ValidationResult() { object Perfect : Valid() data class WithWarnings(val warnings: List<String>) : Valid() } sealed class Invalid : ValidationResult() { data class FieldErrors(val errors: Map<String, String>) : Invalid() data class BusinessRuleViolation(val rule: String) : Invalid() } } ```
3. **Pattern Matching Excellence** ```kotlin val message = when (validationResult) { is ValidationResult.Valid.Perfect -> "All good!" is ValidationResult.Valid.WithWarnings -> "Passed with ${result.warnings.size} warnings" is ValidationResult.Invalid.FieldErrors -> "Please correct the fields" is ValidationResult.Invalid.BusinessRuleViolation -> "Business rule violated: ${result.rule}" } ```
## When NOT to Use Sealed Classes
1. **Simple Enumerations** ```kotlin // Don't use sealed class for this sealed class Direction { object North : Direction() object South : Direction() object East : Direction() object West : Direction() }
// Instead, use enum enum class Direction { NORTH, SOUTH, EAST, WEST } ```
2. **Open Type Hierarchies** ```kotlin // Don't use sealed class if new types can be added later sealed class Animal { class Dog : Animal() class Cat : Animal() }
// Instead, use regular inheritance open class Animal class Dog : Animal() class Cat : Animal() ```
## Best Practices
1. **Keep it Focused** ```kotlin sealed class AuthenticationResult { data class Success( val user: User, val token: String ) : AuthenticationResult() sealed class Failure : AuthenticationResult() { object InvalidCredentials : Failure() object AccountLocked : Failure() data class TwoFactorRequired(val method: String) : Failure() } } ```
2. **Use Data Classes for States with Data** ```kotlin sealed class SearchState { object Initial : SearchState() object Searching : SearchState() data class Results( val items: List<Item>, val totalCount: Int ) : SearchState() data class Error( val message: String, val retryable: Boolean ) : SearchState() } ```
3. **Leverage Sealed Class Hierarchies** ```kotlin sealed class ViewEvent { sealed class User : ViewEvent() { data class Clicked(val id: String) : User() data class LongPressed(val id: String) : User() } sealed class System : ViewEvent() { object LowMemory : System() data class Error(val message: String) : System() } } ```
## Real-World Example: Document Processing
```kotlin sealed class Document { abstract val id: String abstract val created: LocalDateTime data class Invoice( override val id: String, override val created: LocalDateTime, val amount: Double, val dueDate: LocalDate ) : Document() data class Report( override val id: String, override val created: LocalDateTime, val title: String, val content: String ) : Document() data class Contract( override val id: String, override val created: LocalDateTime, val parties: List<String>, val terms: List<String> ) : Document() }
class DocumentProcessor { fun process(document: Document) { val result = when (document) { is Document.Invoice -> processInvoice(document) is Document.Report -> processReport(document) is Document.Contract -> processContract(document) } // Compiler ensures all document types are handled } } ```
## Conclusion
Sealed classes are most appropriate when: 1. You have a finite set of possible types 2. Different types need to carry different data 3. You want compile-time exhaustiveness checking 4. You need to represent hierarchical state machines 5. You're handling domain-specific results or errors
They provide a powerful tool for creating robust, type-safe applications while maintaining code readability and maintainability.
Sealed classes represent restricted class hierarchies where all subtypes must be defined within the same file.
They're especially useful for representing limited sets of possibilities.
```kotlin sealed class Result { data class Success(val data: String) : Result() data class Error(val message: String) : Result() object Loading : Result() }
fun handleResult(result: Result) { when (result) { is Result.Success -> println("Success: ${result.data}") is Result.Error -> println("Error: ${result.message}") is Result.Loading -> println("Loading...") // No 'else' needed - compiler knows all possibilities } }
This implementation demonstrates:
Complete Sealed Class Hierarchy
Success case with data
Error case with message
Loading state as object
Practical Usage Examples
Repository pattern
Service layer
Multiple result handlers
Pattern matching
Features
Simulated network operations
Detailed error handling
Timestamp logging
Want to print your doc?
This is not the way.
Try clicking the ⋯ next to your doc name or using a keyboard shortcut (
CtrlP
) instead.