Skip to content
Share
Explore

MADS4013 Intro to KOTLIN Feb 22 w24

Text Reference: Big Nerd Ranch Guide: Chapter 13, 14, 15
Our Course Outline for today states:
Classes
Objects
Constructors
Inheritance
Composition

We will be looking at lots of implementations of Classes and Objects.

Learning Outcomes: Kotlin Object Oriented Programming with:

Classes
Objects
Constructors

A KOTLIN Application, like any OO Language application is Community of Objects
How do we do the data plumbing to connect Objects?
Classes have 2 kinds of members:
Data Fields (attributes):
1. Scalar or Primitives
2. OBJECT of another class → this is Composition has-a
Methods

CompositionShared Field Composition
Inheritance
Method Calls



megaphone

Your Simplest KOTLIN OO Program:


// Define a class called Person
class Person(val name: String, val age: Int) {
// Define a method called speak
fun speak() {
println("Hello, my name is $name and I am $age years old.")
}
}

fun main() {
// Create an instance of the Person class
val person = Person("John", 25)
// Call the speak method on the person instance
person.speak()
}




info

## Lab Workbook: Object-Oriented Kotlin

**Objective:** Understand the fundamentals of object-oriented programming (OOP) and how Kotlin supports OOP concepts.
In this lab workbook, you will explore essential concepts of object-oriented programming using the Kotlin programming language. The topics covered include:
- classes: are templates → Purpose of a class is to model real world entities
2 kinds of entities:
Tangible
Intangible entities :
What is a calendar entry? A relationship between people, an event, a location, datetime
objects - are instatiations of Classes
encapsulation : walled garden: visiblity on data fields: → ONLY METHODS in that class can read or mutable that variable.
Tracing method calls is some we have good tool support for (Object Interaction Diagrams)
constructors: Special Functions which are run when you construct and OBJECT: we can pre-set data fields in objects upon instantiation.
Building Communities of objects:
method calls between objects with method parameters (variables we are sending to the Called Object) - method can have a return value/ passing and return values
object composition: has-a relationships
inheritance: is-a relationships


Lab 1: Classes and Objects

**Objective:** Learn about classes, objects, and how to create them in Kotlin.
1. **Understanding Classes and Objects** - Definition of classes and objects in object-oriented programming. - The role of classes as blueprints for creating objects.
2. **Creating Objects in Kotlin** - Instantiating objects from classes in Kotlin. - Accessing object properties and methods.

Lab 2: Encapsulation and Private Data Fields: Building the Walled Garden

**Objective:** Understand the concept of encapsulation, private data fields, and the use of getter and setter methods for protecting state.
1. **Encapsulation and Walled Garden Metaphor** - Explanation of encapsulation and its role in protecting the internal state of objects. - Introducing the metaphor of a walled garden to illustrate encapsulation.
2. **Private Data Fields with Getter and Setter Methods** - Defining private data fields within a class. - Creating getter and setter methods to access and modify private data fields.

Lab 3: Constructors and Object Initialization

**Objective:** Learn about constructors and their role in initializing objects.
1. **Introduction to Constructors** - Explanation of constructors and their importance in object initialization. - Different types of constructors in Kotlin.
2. **Using Constructors** - Creating objects using different types of constructors. - Understanding how constructors initialize object properties.

Lab 4: Method Calls, Data Passing, and Return Values

Objective: Explore connecting objects through method calls, passing data, and returning values.
1. **Connecting Objects with Method Calls** - Invoking methods on objects to perform actions and operations. - Passing data as method parameters and receiving return values.
2. **Object Composition** - Understanding how objects can be composed of other objects. - Demonstrating the relationship between objects through composition.

Lab 5: Inheritance

Objective: Understand the concept of inheritance and its role in object-oriented programming.
1. **Introduction to Inheritance** - Explanation of inheritance and its benefits in code reusability. - Creating a class hierarchy with superclass and subclasses.
2. **Implementing Inheritance in Kotlin** - Extending classes to create subclasses. - Overriding methods in subclasses to provide specialized behavior.
To get a vision of how to use Inheritance:
One of the core OO principles is ABSTRACTION.

Note:

Field Composition models “has a” relationships. A car has a radio.
Inheritance models “is a” relationships. A Tiger is a Feline.



minus

Lab 1: Understanding Classes and Objects

In object-oriented programming, classes and objects play a fundamental role in structuring a software program into reusable pieces of code. Let's explore the definition of classes and objects and their significance as blueprints for creating objects through several progressive examples.
Definition of Classes and Objects
In object-oriented programming (OOP), a class serves as an abstract blueprint for creating more specific, concrete objects.
Classes and Objects model real world tangible and intangible entities. The power of OO programming is that we can model relationships with Inheritance and Composition - these are the 2 key fundemental ways to create data plumbing between objects to create Applications - which are Communities of Objects.
Just like an architect's blueprint for building a house defines the structure, layout, and shape of the house, a class defines the structure and behavior of an object. It acts as a recipe for creating objects, specifying the properties the object will have and what it can do. Classes allow you to create objects that behave in a consistent and predictable way.
Role of Classes as Blueprints
Classes define the attributes and methods of objects. Attributes represent what the class looks like, while methods represent what the class does. When you create an object from a class, you are essentially creating an instance of that class with its own set of attributes and methods.
Classes and objects in object-oriented programming (OOP) serve as powerful tools for modeling real-world entities, both tangible and intangible. With OOP, we can create software applications that mimic the relationships and interactions between these entities, resulting in a more intuitive and efficient development process. Two key fundamental concepts in OOP that enable us to model these relationships are inheritance and composition.
Inheritance allows us to establish a hierarchical relationship between classes, where a subclass inherits properties and behaviors from a superclass. This allows us to reuse and extend existing code by creating specialized classes that inherit common characteristics from a more general class. Inheritance models the "is-a" relationship, where a subclass is a more specific type of the superclass. For example, in a banking application, we can have a general class called "Account" as the superclass, and more specific classes like "SavingsAccount" and "CheckingAccount" as subclasses, inheriting properties and methods from the "Account" class.
Composition, on the other hand, allows us to create complex objects by combining simpler objects or components. With composition, we can build more intricate systems by assembling objects to create new functionality. Composition models the "has-a" relationship, where an object contains other objects as its parts or components. For example, in a car rental system, we can have a "Car" class and a "Customer" class. The "Customer" class can have a composition relationship with the "Car" class, where each customer can have one or more cars associated with their account.
By leveraging inheritance and composition, we can create data plumbing between objects, enabling the development of robust and scalable applications. These applications can be seen as communities of objects, where each object plays a specific role and interacts with other objects to achieve a common goal. This approach allows us to model complex real-world scenarios effectively, resulting in software that is more maintainable, extensible, and closely aligned with the real-world domain.

Let's now continue with the detailed outline for each lab, starting with constructors and object initialization.

Example 1: Creating a Simple Class

// Define a simple class called Car
class Car {
// Properties
var color: String = "Red"
var model: String = "XYZ"

// Method
fun startEngine() {
println("Engine started")
}
}

fun main() {
// Create an instance of the Car class
val myCar = Car()

// Accessing object properties
println("My car's color is ${myCar.color}")
println("My car's model is ${myCar.model}")

// Calling a method on the object
myCar.startEngine()
}


Example 2: Using Class as a Blueprint

// Define a class called Dog
class Dog {
// Properties
var breed: String = "Labrador"
var age: Int = 3

// Method
fun bark() {
println("Woof! Woof!")
}
}

fun main() {
// Create an instance of the Dog class
val myDog = Dog()

// Accessing object properties
println("My dog's breed is ${myDog.breed}")
println("My dog's age is ${myDog.age}")

// Calling a method on the object
myDog.bark()
}


These examples demonstrate the creation of classes and the instantiation of objects in Kotlin. They serve as a starting point for understanding the role of classes as blueprints for creating objects.

error

Lab 2: Encapsulation and Private Data Fields: Building the Walled Garden

Example: Encapsulation and Private Data Fields
// Define a class using encapsulation to protect internal state
class Person {
// Private data fields
private var name: String = "John"
private var age: Int = 30

// Getter methods to access private data fields
fun getName(): String {
return name
}

fun getAge(): Int {
return age
}

// Setter methods to modify private data fields
fun setName(newName: String) {
name = newName
}

fun setAge(newAge: Int) {
if (newAge > 0) {
age = newAge
}
}
}

fun main() {
// Create an instance of the Person class
val person = Person()

// Accessing private data fields using getter methods
println("Person's name: ${person.getName()}")
println("Person's age: ${person.getAge()}")

// Modifying private data fields using setter methods
person.setName("Alice")
person.setAge(25)

// Display the updated information
println("Updated name: ${person.getName()}")
println("Updated age: ${person.getAge()}")
}

image.png
Next, we will proceed to detail the concept of encapsulation and private data fields with getter and setter methods to protect state integrity and enforce encapsulation.
// Define a class using encapsulation with a constructor
class BankAccount(private var accountNumber: String, private var balance: Double) {
// Getter methods
fun getAccountNumber(): String {
return accountNumber
}

fun getBalance(): Double {
return balance
}

// Setter method to deposit funds
fun deposit(amount: Double) {
if (amount > 0) {
balance += amount
println("Deposit of $amount successful. New balance: $balance")
}
}

// Setter method to withdraw funds
fun withdraw(amount: Double) {
if (amount > 0 && amount <= balance) {
balance -= amount
println("Withdrawal of $amount successful. New balance: $balance")
} else {
println("Insufficient funds for withdrawal")
}
}
}

fun main() {
// Create a BankAccount object using the constructor
val account = BankAccount("123456789", 1000.0)

// Accessing private data fields using getter methods
println("Account Number: ${account.getAccountNumber()}")
println("Current Balance: ${account.getBalance()}")

// Deposit and withdraw funds
account.deposit(500.0)
account.withdraw(200.0)
}

These examples demonstrate the use of encapsulation, private data fields, and getter/setter methods to protect the internal state of object integrity in Kotlin.
They serve as a foundation for understanding the importance of encapsulation and the role of getter and setter methods in maintaining object integrity.

minus

Lab 3: Constructors and Object Initialization

Example: Encapsulation with Constructor
A constructor in object-oriented programming is a special method that is automatically invoked when an object of a class is created.
Its primary purpose is to initialize the data members of new objects.
In Kotlin, the constructor is used to set initial values for the object's attributes.
The role and function of the constructor are crucial in the process of object initialization.
When an object is created, the constructor is responsible for ensuring that the object is in a valid state by initializing its properties. Constructors can be parameterized, allowing them to accept arguments that are used to initialize the object's attributes with specific values.
Example: Role and Function of the Constructor
// Define a class with a parameterized constructor
class Person(private name: String, private age: Int) {
// Properties
var personName: String = name
var personAge: Int = age

// Initializer of the object block
// init {
// use init lifecycle phase to check that necessary pre-conditions are true.
// println("Person object created with name: $personName and age: // $personAge")
// }
}

fun main() {
// Create an instance of the Person class using the constructor
val person1 = Person("Alice", 25)
val person2 = Person("Bob", 30)
}

#Best_Practice Make data fields private
use setters and getters: enforce business rules in the execution of code



In this example, the Person class has a parameterized constructor that accepts the name and age as arguments. When objects of the Person class are created, the constructor is automatically invoked, and the provided arguments are used to initialize the object's properties.
The init block within the class is also executed when the object is initialized.
The constructor plays a vital role in ensuring that objects are properly initialized with the required data, setting the stage for the objects to be used effectively within the application.
By understanding the role and function of the constructor, you will be able to create well-structured and properly initialized objects, laying a strong foundation for building robust and reliable applications.


info

Lab 4: Method Calls, Data Passing, and Return Values.

Here are some programs to illustrate the concepts of connecting objects with method calls, passing data, returning values, and object composition:

Example: Connecting Objects with Method Calls
// Define a class called Calculator
class Calculator {
// Method to calculate the sum of two numbers
fun add(num1: Int, num2: Int): Int {
return num1 + num2
}
}

// Define a class called MathApp
class MathApp {
// Method to use the Calculator object to perform addition
fun performAddition() {
val calculator = Calculator()
val result = calculator.add(5, 3)
println("The sum of 5 and 3 is $result")
}
}

fun main() {
// Create an instance of the MathApp class and call the performAddition method
val app = MathApp()
app.performAddition()
}

In this example, we have a Calculator class that defines a method add to calculate the sum of two numbers. The MathApp class uses an instance of the Calculator class to perform addition by invoking the add method. The result is then printed to the console.
Example: Object Composition: In composition, we can make a field of OBJECT TYPE
// Define a class called Engine
class Engine {
// Method to start the engine
fun start() {
println("Engine started")
}
}

// Define a class called Car that has an Engine object as a property
class Car(private val engine: Engine) {
// Method to start the car by invoking the start method of the Engine object
fun startCar() {
engine.start()
println("Car started")
}
}

fun main() {
// Create an instance of the Engine class
val engine = Engine()

// Create an instance of the Car class and pass the engine object as a parameter
val car = Car(engine)

// Call the startCar method to start the car
car.startCar()
}


2 key heuristics in Software Design: SOLID Principles
High Cohesion: 1 class does 1 job {I only need to make change to this one class when we make changes to business rules}.
Low Coupling: Minimize number of method calls
Motivation for Encapsulation (in terms of SOLID) is to have a smaller number of classes to change when we need to implement new business rules.

In this example, we have an Engine class with a method start that prints "Engine started" to the console.
The Car class has an Engine object as a property and a method startCar that invokes the start method of the Engine object. When we create an instance of the Car class, we pass the engine object as a parameter. Finally, we call the startCar method to start the car, which in turn starts the engine.
These examples demonstrate how objects can be connected through method calls, data can be passed as parameters, and return values can be received.
They also showcase the concept of object composition, where one object contains another object as a property, forming a relationship between the objects.
By exploring these examples, you will gain a better understanding of how objects can interact and collaborate within a program.
megaphone

Lab 5: Inheritance

Here are several progressive example programs to illustrate the concepts of inheritance and its implementation in Kotlin:

Example 1: Introduction to Inheritance

// Define a superclass called Animal
// SUPERCLASS is the highest level of abstraction
// where you park all the commonalities among all the subclasses
open class Animal { // Method to make the animal sound open fun makeSound() { println("The animal makes a sound.") } }

// If I inherit from a Super Class, all the methods which are available in the SuperClass will be available in my Sub Class
// if I DO NOT in my subclass over-ride the super class definition, then that super class will be // available in my subclass.
// I CAN in sub class over ride the method definition from the Super Class.

// Define a subclass called Dog that inherits from Animal class Dog : Animal() { // Override the makeSound method to provide specialized behavior override fun makeSound() { println("The dog barks.") } }

// polymorphism = methods can change, either implementation or signature - in the Sub Class
// method over riding: varying the method signature: Number of parameters and types of
// parameters.
// method over-loading : changing the method implementation
fun main() { // Create an instance of the Dog class and call the makeSound method val dog = Dog() dog.makeSound() } ```
In this example, we have a superclass called `Animal` that defines a method `makeSound` to make a generic animal sound. The `Dog` class is a subclass of `Animal` and overrides the `makeSound` method to provide specialized behavior for a dog. When we create an instance of the `Dog` class and call the `makeSound` method, it prints "The dog barks" to the console.

Example 2: Implementing Inheritance in Kotlin

// Define a superclass called Shape open class Shape { // Method to calculate the area of the shape open fun calculateArea(): Double { return 0.0 } }
// Define a subclass called Rectangle that inherits from Shape class Rectangle(private val length: Double, private val width: Double) : Shape() { // Override the calculateArea method to provide specialized behavior override fun calculateArea(): Double { return length * width } }
fun main() { // Create an instance of the Rectangle class and call the calculateArea method val rectangle = Rectangle(5.0, 3.0) val area = rectangle.calculateArea() println("The area of the rectangle is $area") } ```
In this example, we have a superclass called `Shape` that defines a method `calculateArea` to calculate the area of a generic shape.
The `Rectangle` class is a subclass of `Shape` and overrides the `calculateArea` method to provide specialized behavior for a rectangle.
When we create an instance of the `Rectangle` class with specific length and width values, we call the `calculateArea` method, which returns the area of the rectangle.
These examples demonstrate how inheritance allows us to create a class hierarchy, where subclasses inherit properties and behaviors from a superclass. It enables code reuse, as common functionality can be defined in the superclass and specialized behavior can be implemented in the subclasses through method overriding. (Overriding =Present a new implemention of the method in the sub class. Over-loading => changing the method signnature in the subclass.)
By exploring these examples, you will gain a better understanding of inheritance and its role in object-oriented programming.
image.png

Question: How do I know the best way to achieve High Cohesion // Low Coupling?
When do we use Inheritance?
When do we use Field Composition?
I make a super good UMLUnified Modeling Diagram: This will tell me the optimal way to answer those questions.
CASE Computer Aided Software Engineering tools (Rational Rose) → You can draw UML and click a button: Forward engineering : design to code: The Case Tool will generate your Java Code.
Reverse Engineering: feed in code: it will create the UML for you.
Round Trip Engineering.


Kotlin Documentation on Polymorphism:

megaphone

Take the Dog code illustrate adding some (any) other function to class Dog, and then make various progressive examples of polymorphism, method over loading, and method over riding:


open class Animal {

// Method to make the animal sound
open fun makeSound() {
println("The animal makes a sound.")
}
}

class Dog : Animal() {
// note: there is no over-riden makeSound() function -
// so the Parent Class makeSound() method goes to work and does the job.
}

fun main() {
// Create an instance of the Dog class and call the makeSound method
val dog = Dog()
dog.makeSound()
}

Adding Functions to Class Dog

Example 1: Adding a Bark Function
class Dog : Animal() {
// New function added to the Dog class
fun bark() {
println("The dog barks loudly.")
}
}


Example 2: Adding a Fetch Function
class Dog : Animal() {
// New function added to the Dog class
fun fetch() {
println("The dog fetches the ball.")
}
}

Want to print your doc?
This is not the way.
Try clicking the ··· in the right corner or using a keyboard shortcut (
CtrlP
) instead.