Share
Explore

Lab: Kotlin Polymorphism - Method Overloading and Method Overriding

Lab: Kotlin Polymorphism - Method Overloading and Method Overriding
Objective: Understand how to apply method overloading and method overriding in Kotlin to achieve polymorphism in an inheritance context.
The purpose of polymorphism is to provide an enabling mechanism for a subclass to present a method implementation appropriate or its runtime context.
// Define a base class called Shape
// abstract means : You cannot instantiate this abstract class

abstract class Shape {
abstract fun area(): Double
// an abstract function MUST be overriden in the implementing sub class

fun printArea() {
// ready to go out of the box : you don't need to come up with your own method implementation
println("Area: ${area()}")
}
}

// Define a subclass called Rectangle that overrides the area method
class Rectangle(width: Double, height: Double) : Shape() {
override fun area(): Double {
return width * height
}
}

// Define another subclass called Circle that overrides the area method
class Circle(radius: Double) : Shape() {
override fun area(): Double {
return Math.PI * radius * radius
}
}

// over-ride means : change the implementation in the subclass
// over-load means : change the method signature in the sub class
// what is the method signature?
// method name MUST remain the same
// what you can change is: the method's parametric signature:
// methodName(signature : which is the number of and types of the parameters you pass // into the method on method invocation or call.

// Define a class called Calculator that overloads the calculate method
class Calculator {
fun calculate(shape: Shape) {
shape.printArea()
}

fun calculate(rectangle: Rectangle) {
println("Rectangle area: ${rectangle.area()}")
}

fun calculate(circle: Circle) {
println("Circle area: ${circle.area()}")
}
}

// Create objects and call methods
val rectangle = Rectangle(5.0, 3.0)
val circle = Circle(4.0)

val calculator = Calculator()
calculator.calculate(rectangle) // Output: Rectangle area: 15.0
calculator.calculate(circle) // Output: Circle area: 50.26548245743669

calculator.calculate(rectangle as Shape) // Output: Area: 15.0
calculator.calculate(circle as Shape) // Output: Area: 50.26548245743669

Corrected Code Version:
issue with the Rectangle and Circle classes.
The width, height, and radius parameters are not properly stored as properties.
Here's the corrected version:
WHAT TO LEARN FROM THIS / WHAT IS THE TAKE-AWAY:
THE OPERATION OF METHOD OVER LOADING

// Define a base class called Shape
abstract class Shape {
abstract fun area(): Double

fun printArea() {
println("Area: ${area()}")
}
}

// Define a subclass called Rectangle that overrides the area method
class Rectangle(private val width: Double, private val height: Double) : Shape() {
override fun area(): Double {
return width * height
}
}

// Define another subclass called Circle that overrides the area method
class Circle(private val radius: Double) : Shape() {
override fun area(): Double {
return Math.PI * radius * radius
}
}

// Define a class called Calculator that overloads the calculate method
class Calculator {
fun calculate(shape: Shape) {
shape.printArea()
}

fun calculate(rectangle: Rectangle) {
println("Rectangle area: ${rectangle.area()}")
}

fun calculate(circle: Circle) {
println("Circle area: ${circle.area()}")
}
}

fun main() {
// Create objects and call methods
val rectangle = Rectangle(5.0, 3.0)
val circle = Circle(4.0)

val calculator = Calculator()
calculator.calculate(rectangle) // Output: Rectangle area: 15.0
calculator.calculate(circle) // Output: Circle area: 50.26548245743669

calculator.calculate(rectangle as Shape) // Output: Area: 15.0
calculator.calculate(circle as Shape) // Output: Area: 50.26548245743669
}
The changes made are:
In the Rectangle class, width and height are now declared as private properties in the primary constructor.
In the Circle class, radius is now declared as a private property in the primary constructor.
Added a main() function to demonstrate the usage of the classes.
These changes ensure that the width, height, and radius values are properly stored and accessible within their respective classes. The rest of the code remains the same and should work as expected.



Questions:
How does method overriding achieve polymorphism in the Shape hierarchy?
How does method overloading achieve polymorphism in the Calculator class?
What is the benefit of using polymorphism in this example?
Conclusion:
In this lab, we demonstrated how method overloading and method overriding achieve polymorphism in Kotlin. We defined a base class Shape with an abstract area method, which was overridden by subclasses Rectangle and Circle. We also defined a Calculator class that overloads the calculate method to work with different shapes. By using polymorphism, we can write more flexible and reusable code that works with various shapes without knowing their specific types.
Additional Resources:
[Kotlin Documentation: Polymorphism]((link unavailable))
[Kotlin Documentation: Method Overloading]((link unavailable))
[Kotlin Documentation: Method Overriding]((link unavailable))


Let's apply the OODA (Observe, Orient, Decide, Act) framework to analyze this lab on Kotlin Polymorphism, focusing on method overloading and method overriding.
1. Observe - We need to model different shapes with varying area calculations. - We want to create a calculator that can work with different shapes. - Kotlin provides mechanisms for both method overriding and method overloading. - We have a hierarchy of shapes and a separate calculator class.
2. Orient - We recognize that different shapes require different area calculations. - We understand that method overriding allows subclasses to provide specific implementations of methods defined in their superclass. - We see that method overloading allows a class to have multiple methods with the same name but different parameters. - We realize that polymorphism can be achieved through both inheritance (method overriding) and interface implementation.
3. Decide: - We choose to use an abstract base class Shape with an abstract area method. - We decide to create concrete subclasses (Rectangle and Circle) that override the area method. - We opt to create a Calculator class with overloaded calculate methods for different shape types. - We decide to demonstrate polymorphism by using both specific and base class references.
4. Act: - We implement the solution as follows:
1. Define the abstract Shape class: ```kotlin abstract class Shape { abstract fun area(): Double fun printArea() { println("Area: ${area()}") } } ```
2. Create concrete subclasses overriding the area method: ```kotlin class Rectangle(width: Double, height: Double) : Shape() { override fun area(): Double { return width * height } }
class Circle(radius: Double) : Shape() { override fun area(): Double { return Math.PI * radius * radius } } ```
3. Define the Calculator class with overloaded methods: ```kotlin class Calculator { fun calculate(shape: Shape) { shape.printArea() } fun calculate(rectangle: Rectangle) { println("Rectangle area: ${rectangle.area()}") } fun calculate(circle: Circle) { println("Circle area: ${circle.area()}") } } ```
4. Demonstrate usage with polymorphism: ```kotlin val rectangle = Rectangle(5.0, 3.0) val circle = Circle(4.0) val calculator = Calculator() calculator.calculate(rectangle) calculator.calculate(circle) calculator.calculate(rectangle as Shape) calculator.calculate(circle as Shape) ```
Addressing the questions:
1. Method overriding achieves polymorphism in the Shape hierarchy by allowing subclasses (Rectangle and Circle) to provide their own implementations of the area method. This enables objects of different subclasses to be treated as objects of the base class Shape, with the correct area calculation being called based on the actual object type.
2. Method overloading achieves polymorphism in the Calculator class by providing multiple calculate methods that can handle different types of shapes. The appropriate method is called based on the type of the argument passed, allowing the Calculator to work with different shapes without needing to know their specific types. Basically : Method is when you change the method’s parameter signature.
3. The benefits of using polymorphism in this example include: - Flexibility: The code can work with different types of shapes without modification. - Extensibility: New shape types can be easily added by creating new subclasses of Shape. - Algorithm reuse: Common behavior (like printArea) can be defined once in the base class. - Improved readability: The code expresses the intent clearly, with each shape responsible for its own area calculation.
This OODA analysis reveals how polymorphism, through both method overriding and overloading, allows for flexible and extensible code design. It demonstrates how Kotlin's object-oriented features can be used to model complex systems with varying behaviors while maintaining a clean and organized code structure.
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.